1 /*
2 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package javax.swing.text.html;
26
27 import sun.swing.SwingUtilities2;
28 import java.util.*;
29 import java.awt.*;
30 import java.io.*;
31 import java.net.*;
32 import javax.swing.Icon;
33 import javax.swing.ImageIcon;
34 import javax.swing.UIManager;
35 import javax.swing.border.*;
36 import javax.swing.event.ChangeListener;
37 import javax.swing.text.*;
38
39 /**
40 * Support for defining the visual characteristics of
41 * HTML views being rendered. The StyleSheet is used to
42 * translate the HTML model into visual characteristics.
43 * This enables views to be customized by a look-and-feel,
44 * multiple views over the same model can be rendered
45 * differently, etc. This can be thought of as a CSS
46 * rule repository. The key for CSS attributes is an
47 * object of type CSS.Attribute. The type of the value
48 * is up to the StyleSheet implementation, but the
49 * <code>toString</code> method is required
50 * to return a string representation of CSS value.
51 * <p>
52 * The primary entry point for HTML View implementations
53 * to get their attributes is the
54 * {@link #getViewAttributes getViewAttributes}
55 * method. This should be implemented to establish the
56 * desired policy used to associate attributes with the view.
57 * Each HTMLEditorKit (i.e. and therefore each associated
58 * JEditorPane) can have its own StyleSheet, but by default one
59 * sheet will be shared by all of the HTMLEditorKit instances.
60 * HTMLDocument instance can also have a StyleSheet, which
61 * holds the document-specific CSS specifications.
62 * <p>
63 * In order for Views to store less state and therefore be
64 * more lightweight, the StyleSheet can act as a factory for
65 * painters that handle some of the rendering tasks. This allows
66 * implementations to determine what they want to cache
67 * and have the sharing potentially at the level that a
68 * selector is common to multiple views. Since the StyleSheet
69 * may be used by views over multiple documents and typically
70 * the HTML attributes don't effect the selector being used,
71 * the potential for sharing is significant.
72 * <p>
73 * The rules are stored as named styles, and other information
74 * is stored to translate the context of an element to a
75 * rule quickly. The following code fragment will display
76 * the named styles, and therefore the CSS rules contained.
77 * <code><pre>
78 *
79 * import java.util.*;
80 * import javax.swing.text.*;
81 * import javax.swing.text.html.*;
82 *
83 * public class ShowStyles {
84 *
85 * public static void main(String[] args) {
86 * HTMLEditorKit kit = new HTMLEditorKit();
87 * HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
88 * StyleSheet styles = doc.getStyleSheet();
89 *
90 * Enumeration rules = styles.getStyleNames();
91 * while (rules.hasMoreElements()) {
92 * String name = (String) rules.nextElement();
93 * Style rule = styles.getStyle(name);
94 * System.out.println(rule.toString());
95 * }
96 * System.exit(0);
97 * }
98 * }
99 *
100 * </pre></code>
101 * <p>
102 * The semantics for when a CSS style should overide visual attributes
103 * defined by an element are not well defined. For example, the html
104 * <code><body bgcolor=red></code> makes the body have a red
105 * background. But if the html file also contains the CSS rule
106 * <code>body { background: blue }</code> it becomes less clear as to
107 * what color the background of the body should be. The current
108 * implemention gives visual attributes defined in the element the
109 * highest precedence, that is they are always checked before any styles.
110 * Therefore, in the previous example the background would have a
111 * red color as the body element defines the background color to be red.
112 * <p>
113 * As already mentioned this supports CSS. We don't support the full CSS
114 * spec. Refer to the javadoc of the CSS class to see what properties
115 * we support. The two major CSS parsing related
116 * concepts we do not currently
117 * support are pseudo selectors, such as <code>A:link { color: red }</code>,
118 * and the <code>important</code> modifier.
119 * <p>
120 * <font color="red">Note: This implementation is currently
121 * incomplete. It can be replaced with alternative implementations
122 * that are complete. Future versions of this class will provide
123 * better CSS support.</font>
124 *
125 * @author Timothy Prinzing
126 * @author Sunita Mani
127 * @author Sara Swanson
128 * @author Jill Nakata
129 */
130 public class StyleSheet extends StyleContext {
131 // As the javadoc states, this class maintains a mapping between
132 // a CSS selector (such as p.bar) and a Style.
133 // This consists of a number of parts:
134 // . Each selector is broken down into its constituent simple selectors,
135 // and stored in an inverted graph, for example:
136 // p { color: red } ol p { font-size: 10pt } ul p { font-size: 12pt }
137 // results in the graph:
138 // root
139 // |
140 // p
141 // / \
142 // ol ul
143 // each node (an instance of SelectorMapping) has an associated
144 // specificity and potentially a Style.
145 // . Every rule that is asked for (either by way of getRule(String) or
146 // getRule(HTML.Tag, Element)) results in a unique instance of
147 // ResolvedStyle. ResolvedStyles contain the AttributeSets from the
148 // SelectorMapping.
149 // . When a new rule is created it is inserted into the graph, and
150 // the AttributeSets of each ResolvedStyles are updated appropriately.
151 // . This class creates special AttributeSets, LargeConversionSet and
152 // SmallConversionSet, that maintain a mapping between StyleConstants
153 // and CSS so that developers that wish to use the StyleConstants
154 // methods can do so.
155 // . When one of the AttributeSets is mutated by way of a
156 // StyleConstants key, all the associated CSS keys are removed. This is
157 // done so that the two representations don't get out of sync. For
158 // example, if the developer adds StyleConsants.BOLD, FALSE to an
159 // AttributeSet that contains HTML.Tag.B, the HTML.Tag.B entry will
160 // be removed.
161
162 /**
163 * Construct a StyleSheet
164 */
165 public StyleSheet() {
166 super();
167 selectorMapping = new SelectorMapping(0);
168 resolvedStyles = new Hashtable<String, ResolvedStyle>();
169 if (css == null) {
170 css = new CSS();
171 }
172 }
173
174 /**
175 * Fetches the style to use to render the given type
176 * of HTML tag. The element given is representing
177 * the tag and can be used to determine the nesting
178 * for situations where the attributes will differ
179 * if nesting inside of elements.
180 *
181 * @param t the type to translate to visual attributes
182 * @param e the element representing the tag; the element
183 * can be used to determine the nesting for situations where
184 * the attributes will differ if nested inside of other
185 * elements
186 * @return the set of CSS attributes to use to render
187 * the tag
188 */
189 public Style getRule(HTML.Tag t, Element e) {
190 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
191
192 try {
193 // Build an array of all the parent elements.
194 Vector<Element> searchContext = sb.getVector();
195
196 for (Element p = e; p != null; p = p.getParentElement()) {
197 searchContext.addElement(p);
198 }
199
200 // Build a fully qualified selector.
201 int n = searchContext.size();
202 StringBuffer cacheLookup = sb.getStringBuffer();
203 AttributeSet attr;
204 String eName;
205 Object name;
206
207 // >= 1 as the HTML.Tag for the 0th element is passed in.
208 for (int counter = n - 1; counter >= 1; counter--) {
209 e = searchContext.elementAt(counter);
210 attr = e.getAttributes();
211 name = attr.getAttribute(StyleConstants.NameAttribute);
212 eName = name.toString();
213 cacheLookup.append(eName);
214 if (attr != null) {
215 if (attr.isDefined(HTML.Attribute.ID)) {
216 cacheLookup.append('#');
217 cacheLookup.append(attr.getAttribute
218 (HTML.Attribute.ID));
219 }
220 else if (attr.isDefined(HTML.Attribute.CLASS)) {
221 cacheLookup.append('.');
222 cacheLookup.append(attr.getAttribute
223 (HTML.Attribute.CLASS));
224 }
225 }
226 cacheLookup.append(' ');
227 }
228 cacheLookup.append(t.toString());
229 e = searchContext.elementAt(0);
230 attr = e.getAttributes();
231 if (e.isLeaf()) {
232 // For leafs, we use the second tier attributes.
233 Object testAttr = attr.getAttribute(t);
234 if (testAttr instanceof AttributeSet) {
235 attr = (AttributeSet)testAttr;
236 }
237 else {
238 attr = null;
239 }
240 }
241 if (attr != null) {
242 if (attr.isDefined(HTML.Attribute.ID)) {
243 cacheLookup.append('#');
244 cacheLookup.append(attr.getAttribute(HTML.Attribute.ID));
245 }
246 else if (attr.isDefined(HTML.Attribute.CLASS)) {
247 cacheLookup.append('.');
248 cacheLookup.append(attr.getAttribute
249 (HTML.Attribute.CLASS));
250 }
251 }
252
253 Style style = getResolvedStyle(cacheLookup.toString(),
254 searchContext, t);
255 return style;
256 }
257 finally {
258 SearchBuffer.releaseSearchBuffer(sb);
259 }
260 }
261
262 /**
263 * Fetches the rule that best matches the selector given
264 * in string form. Where <code>selector</code> is a space separated
265 * String of the element names. For example, <code>selector</code>
266 * might be 'html body tr td''<p>
267 * The attributes of the returned Style will change
268 * as rules are added and removed. That is if you to ask for a rule
269 * with a selector "table p" and a new rule was added with a selector
270 * of "p" the returned Style would include the new attributes from
271 * the rule "p".
272 */
273 public Style getRule(String selector) {
274 selector = cleanSelectorString(selector);
275 if (selector != null) {
276 Style style = getResolvedStyle(selector);
277 return style;
278 }
279 return null;
280 }
281
282 /**
283 * Adds a set of rules to the sheet. The rules are expected to
284 * be in valid CSS format. Typically this would be called as
285 * a result of parsing a <style> tag.
286 */
287 public void addRule(String rule) {
288 if (rule != null) {
289 //tweaks to control display properties
290 //see BasicEditorPaneUI
291 final String baseUnitsDisable = "BASE_SIZE_DISABLE";
292 final String baseUnits = "BASE_SIZE ";
293 final String w3cLengthUnitsEnable = "W3C_LENGTH_UNITS_ENABLE";
294 final String w3cLengthUnitsDisable = "W3C_LENGTH_UNITS_DISABLE";
295 if (rule == baseUnitsDisable) {
296 sizeMap = sizeMapDefault;
297 } else if (rule.startsWith(baseUnits)) {
298 rebaseSizeMap(Integer.
299 parseInt(rule.substring(baseUnits.length())));
300 } else if (rule == w3cLengthUnitsEnable) {
301 w3cLengthUnits = true;
302 } else if (rule == w3cLengthUnitsDisable) {
303 w3cLengthUnits = false;
304 } else {
305 CssParser parser = new CssParser();
306 try {
307 parser.parse(getBase(), new StringReader(rule), false, false);
308 } catch (IOException ioe) { }
309 }
310 }
311 }
312
313 /**
314 * Translates a CSS declaration to an AttributeSet that represents
315 * the CSS declaration. Typically this would be called as a
316 * result of encountering an HTML style attribute.
317 */
318 public AttributeSet getDeclaration(String decl) {
319 if (decl == null) {
320 return SimpleAttributeSet.EMPTY;
321 }
322 CssParser parser = new CssParser();
323 return parser.parseDeclaration(decl);
324 }
325
326 /**
327 * Loads a set of rules that have been specified in terms of
328 * CSS1 grammar. If there are collisions with existing rules,
329 * the newly specified rule will win.
330 *
331 * @param in the stream to read the CSS grammar from
332 * @param ref the reference URL. This value represents the
333 * location of the stream and may be null. All relative
334 * URLs specified in the stream will be based upon this
335 * parameter.
336 */
337 public void loadRules(Reader in, URL ref) throws IOException {
338 CssParser parser = new CssParser();
339 parser.parse(ref, in, false, false);
340 }
341
342 /**
343 * Fetches a set of attributes to use in the view for
344 * displaying. This is basically a set of attributes that
345 * can be used for View.getAttributes.
346 */
347 public AttributeSet getViewAttributes(View v) {
348 return new ViewAttributeSet(v);
349 }
350
351 /**
352 * Removes a named style previously added to the document.
353 *
354 * @param nm the name of the style to remove
355 */
356 public void removeStyle(String nm) {
357 Style aStyle = getStyle(nm);
358
359 if (aStyle != null) {
360 String selector = cleanSelectorString(nm);
361 String[] selectors = getSimpleSelectors(selector);
362 synchronized(this) {
363 SelectorMapping mapping = getRootSelectorMapping();
364 for (int i = selectors.length - 1; i >= 0; i--) {
365 mapping = mapping.getChildSelectorMapping(selectors[i],
366 true);
367 }
368 Style rule = mapping.getStyle();
369 if (rule != null) {
370 mapping.setStyle(null);
371 if (resolvedStyles.size() > 0) {
372 Enumeration<ResolvedStyle> values = resolvedStyles.elements();
373 while (values.hasMoreElements()) {
374 ResolvedStyle style = values.nextElement();
375 style.removeStyle(rule);
376 }
377 }
378 }
379 }
380 }
381 super.removeStyle(nm);
382 }
383
384 /**
385 * Adds the rules from the StyleSheet <code>ss</code> to those of
386 * the receiver. <code>ss's</code> rules will override the rules of
387 * any previously added style sheets. An added StyleSheet will never
388 * override the rules of the receiving style sheet.
389 *
390 * @since 1.3
391 */
392 public void addStyleSheet(StyleSheet ss) {
393 synchronized(this) {
394 if (linkedStyleSheets == null) {
395 linkedStyleSheets = new Vector<StyleSheet>();
396 }
397 if (!linkedStyleSheets.contains(ss)) {
398 int index = 0;
399 if (ss instanceof javax.swing.plaf.UIResource
400 && linkedStyleSheets.size() > 1) {
401 index = linkedStyleSheets.size() - 1;
402 }
403 linkedStyleSheets.insertElementAt(ss, index);
404 linkStyleSheetAt(ss, index);
405 }
406 }
407 }
408
409 /**
410 * Removes the StyleSheet <code>ss</code> from those of the receiver.
411 *
412 * @since 1.3
413 */
414 public void removeStyleSheet(StyleSheet ss) {
415 synchronized(this) {
416 if (linkedStyleSheets != null) {
417 int index = linkedStyleSheets.indexOf(ss);
418 if (index != -1) {
419 linkedStyleSheets.removeElementAt(index);
420 unlinkStyleSheet(ss, index);
421 if (index == 0 && linkedStyleSheets.size() == 0) {
422 linkedStyleSheets = null;
423 }
424 }
425 }
426 }
427 }
428
429 //
430 // The following is used to import style sheets.
431 //
432
433 /**
434 * Returns an array of the linked StyleSheets. Will return null
435 * if there are no linked StyleSheets.
436 *
437 * @since 1.3
438 */
439 public StyleSheet[] getStyleSheets() {
440 StyleSheet[] retValue;
441
442 synchronized(this) {
443 if (linkedStyleSheets != null) {
444 retValue = new StyleSheet[linkedStyleSheets.size()];
445 linkedStyleSheets.copyInto(retValue);
446 }
447 else {
448 retValue = null;
449 }
450 }
451 return retValue;
452 }
453
454 /**
455 * Imports a style sheet from <code>url</code>. The resulting rules
456 * are directly added to the receiver. If you do not want the rules
457 * to become part of the receiver, create a new StyleSheet and use
458 * addStyleSheet to link it in.
459 *
460 * @since 1.3
461 */
462 public void importStyleSheet(URL url) {
463 try {
464 InputStream is;
465
466 is = url.openStream();
467 Reader r = new BufferedReader(new InputStreamReader(is));
468 CssParser parser = new CssParser();
469 parser.parse(url, r, false, true);
470 r.close();
471 is.close();
472 } catch (Throwable e) {
473 // on error we simply have no styles... the html
474 // will look mighty wrong but still function.
475 }
476 }
477
478 /**
479 * Sets the base. All import statements that are relative, will be
480 * relative to <code>base</code>.
481 *
482 * @since 1.3
483 */
484 public void setBase(URL base) {
485 this.base = base;
486 }
487
488 /**
489 * Returns the base.
490 *
491 * @since 1.3
492 */
493 public URL getBase() {
494 return base;
495 }
496
497 /**
498 * Adds a CSS attribute to the given set.
499 *
500 * @since 1.3
501 */
502 public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key,
503 String value) {
504 css.addInternalCSSValue(attr, key, value);
505 }
506
507 /**
508 * Adds a CSS attribute to the given set.
509 *
510 * @since 1.3
511 */
512 public boolean addCSSAttributeFromHTML(MutableAttributeSet attr,
513 CSS.Attribute key, String value) {
514 Object iValue = css.getCssValue(key, value);
515 if (iValue != null) {
516 attr.addAttribute(key, iValue);
517 return true;
518 }
519 return false;
520 }
521
522 // ---- Conversion functionality ---------------------------------
523
524 /**
525 * Converts a set of HTML attributes to an equivalent
526 * set of CSS attributes.
527 *
528 * @param htmlAttrSet AttributeSet containing the HTML attributes.
529 */
530 public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) {
531 AttributeSet cssAttrSet = css.translateHTMLToCSS(htmlAttrSet);
532
533 MutableAttributeSet cssStyleSet = addStyle(null, null);
534 cssStyleSet.addAttributes(cssAttrSet);
535
536 return cssStyleSet;
537 }
538
539 /**
540 * Adds an attribute to the given set, and returns
541 * the new representative set. This is reimplemented to
542 * convert StyleConstant attributes to CSS prior to forwarding
543 * to the superclass behavior. The StyleConstants attribute
544 * has no corresponding CSS entry, the StyleConstants attribute
545 * is stored (but will likely be unused).
546 *
547 * @param old the old attribute set
548 * @param key the non-null attribute key
549 * @param value the attribute value
550 * @return the updated attribute set
551 * @see MutableAttributeSet#addAttribute
552 */
553 public AttributeSet addAttribute(AttributeSet old, Object key,
554 Object value) {
555 if (css == null) {
556 // supers constructor will call this before returning,
557 // and we need to make sure CSS is non null.
558 css = new CSS();
559 }
560 if (key instanceof StyleConstants) {
561 HTML.Tag tag = HTML.getTagForStyleConstantsKey(
562 (StyleConstants)key);
563
564 if (tag != null && old.isDefined(tag)) {
565 old = removeAttribute(old, tag);
566 }
567
568 Object cssValue = css.styleConstantsValueToCSSValue
569 ((StyleConstants)key, value);
570 if (cssValue != null) {
571 Object cssKey = css.styleConstantsKeyToCSSKey
572 ((StyleConstants)key);
573 if (cssKey != null) {
574 return super.addAttribute(old, cssKey, cssValue);
575 }
576 }
577 }
578 return super.addAttribute(old, key, value);
579 }
580
581 /**
582 * Adds a set of attributes to the element. If any of these attributes
583 * are StyleConstants attributes, they will be converted to CSS prior
584 * to forwarding to the superclass behavior.
585 *
586 * @param old the old attribute set
587 * @param attr the attributes to add
588 * @return the updated attribute set
589 * @see MutableAttributeSet#addAttribute
590 */
591 public AttributeSet addAttributes(AttributeSet old, AttributeSet attr) {
592 if (!(attr instanceof HTMLDocument.TaggedAttributeSet)) {
593 old = removeHTMLTags(old, attr);
594 }
595 return super.addAttributes(old, convertAttributeSet(attr));
596 }
597
598 /**
599 * Removes an attribute from the set. If the attribute is a StyleConstants
600 * attribute, the request will be converted to a CSS attribute prior to
601 * forwarding to the superclass behavior.
602 *
603 * @param old the old set of attributes
604 * @param key the non-null attribute name
605 * @return the updated attribute set
606 * @see MutableAttributeSet#removeAttribute
607 */
608 public AttributeSet removeAttribute(AttributeSet old, Object key) {
609 if (key instanceof StyleConstants) {
610 HTML.Tag tag = HTML.getTagForStyleConstantsKey(
611 (StyleConstants)key);
612 if (tag != null) {
613 old = super.removeAttribute(old, tag);
614 }
615
616 Object cssKey = css.styleConstantsKeyToCSSKey((StyleConstants)key);
617 if (cssKey != null) {
618 return super.removeAttribute(old, cssKey);
619 }
620 }
621 return super.removeAttribute(old, key);
622 }
623
624 /**
625 * Removes a set of attributes for the element. If any of the attributes
626 * is a StyleConstants attribute, the request will be converted to a CSS
627 * attribute prior to forwarding to the superclass behavior.
628 *
629 * @param old the old attribute set
630 * @param names the attribute names
631 * @return the updated attribute set
632 * @see MutableAttributeSet#removeAttributes
633 */
634 public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) {
635 // PENDING: Should really be doing something similar to
636 // removeHTMLTags here, but it is rather expensive to have to
637 // clone names
638 return super.removeAttributes(old, names);
639 }
640
641 /**
642 * Removes a set of attributes. If any of the attributes
643 * is a StyleConstants attribute, the request will be converted to a CSS
644 * attribute prior to forwarding to the superclass behavior.
645 *
646 * @param old the old attribute set
647 * @param attrs the attributes
648 * @return the updated attribute set
649 * @see MutableAttributeSet#removeAttributes
650 */
651 public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) {
652 if (old != attrs) {
653 old = removeHTMLTags(old, attrs);
654 }
655 return super.removeAttributes(old, convertAttributeSet(attrs));
656 }
657
658 /**
659 * Creates a compact set of attributes that might be shared.
660 * This is a hook for subclasses that want to alter the
661 * behavior of SmallAttributeSet. This can be reimplemented
662 * to return an AttributeSet that provides some sort of
663 * attribute conversion.
664 *
665 * @param a The set of attributes to be represented in the
666 * the compact form.
667 */
668 protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) {
669 return new SmallConversionSet(a);
670 }
671
672 /**
673 * Creates a large set of attributes that should trade off
674 * space for time. This set will not be shared. This is
675 * a hook for subclasses that want to alter the behavior
676 * of the larger attribute storage format (which is
677 * SimpleAttributeSet by default). This can be reimplemented
678 * to return a MutableAttributeSet that provides some sort of
679 * attribute conversion.
680 *
681 * @param a The set of attributes to be represented in the
682 * the larger form.
683 */
684 protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) {
685 return new LargeConversionSet(a);
686 }
687
688 /**
689 * For any StyleConstants key in attr that has an associated HTML.Tag,
690 * it is removed from old. The resulting AttributeSet is then returned.
691 */
692 private AttributeSet removeHTMLTags(AttributeSet old, AttributeSet attr) {
693 if (!(attr instanceof LargeConversionSet) &&
694 !(attr instanceof SmallConversionSet)) {
695 Enumeration names = attr.getAttributeNames();
696
697 while (names.hasMoreElements()) {
698 Object key = names.nextElement();
699
700 if (key instanceof StyleConstants) {
701 HTML.Tag tag = HTML.getTagForStyleConstantsKey(
702 (StyleConstants)key);
703
704 if (tag != null && old.isDefined(tag)) {
705 old = super.removeAttribute(old, tag);
706 }
707 }
708 }
709 }
710 return old;
711 }
712
713 /**
714 * Converts a set of attributes (if necessary) so that
715 * any attributes that were specified as StyleConstants
716 * attributes and have a CSS mapping, will be converted
717 * to CSS attributes.
718 */
719 AttributeSet convertAttributeSet(AttributeSet a) {
720 if ((a instanceof LargeConversionSet) ||
721 (a instanceof SmallConversionSet)) {
722 // known to be converted.
723 return a;
724 }
725 // in most cases, there are no StyleConstants attributes
726 // so we iterate the collection of keys to avoid creating
727 // a new set.
728 Enumeration names = a.getAttributeNames();
729 while (names.hasMoreElements()) {
730 Object name = names.nextElement();
731 if (name instanceof StyleConstants) {
732 // we really need to do a conversion, iterate again
733 // building a new set.
734 MutableAttributeSet converted = new LargeConversionSet();
735 Enumeration keys = a.getAttributeNames();
736 while (keys.hasMoreElements()) {
737 Object key = keys.nextElement();
738 Object cssValue = null;
739 if (key instanceof StyleConstants) {
740 // convert the StyleConstants attribute if possible
741 Object cssKey = css.styleConstantsKeyToCSSKey
742 ((StyleConstants)key);
743 if (cssKey != null) {
744 Object value = a.getAttribute(key);
745 cssValue = css.styleConstantsValueToCSSValue
746 ((StyleConstants)key, value);
747 if (cssValue != null) {
748 converted.addAttribute(cssKey, cssValue);
749 }
750 }
751 }
752 if (cssValue == null) {
753 converted.addAttribute(key, a.getAttribute(key));
754 }
755 }
756 return converted;
757 }
758 }
759 return a;
760 }
761
762 /**
763 * Large set of attributes that does conversion of requests
764 * for attributes of type StyleConstants.
765 */
766 class LargeConversionSet extends SimpleAttributeSet {
767
768 /**
769 * Creates a new attribute set based on a supplied set of attributes.
770 *
771 * @param source the set of attributes
772 */
773 public LargeConversionSet(AttributeSet source) {
774 super(source);
775 }
776
777 public LargeConversionSet() {
778 super();
779 }
780
781 /**
782 * Checks whether a given attribute is defined.
783 *
784 * @param key the attribute key
785 * @return true if the attribute is defined
786 * @see AttributeSet#isDefined
787 */
788 public boolean isDefined(Object key) {
789 if (key instanceof StyleConstants) {
790 Object cssKey = css.styleConstantsKeyToCSSKey
791 ((StyleConstants)key);
792 if (cssKey != null) {
793 return super.isDefined(cssKey);
794 }
795 }
796 return super.isDefined(key);
797 }
798
799 /**
800 * Gets the value of an attribute.
801 *
802 * @param key the attribute name
803 * @return the attribute value
804 * @see AttributeSet#getAttribute
805 */
806 public Object getAttribute(Object key) {
807 if (key instanceof StyleConstants) {
808 Object cssKey = css.styleConstantsKeyToCSSKey
809 ((StyleConstants)key);
810 if (cssKey != null) {
811 Object value = super.getAttribute(cssKey);
812 if (value != null) {
813 return css.cssValueToStyleConstantsValue
814 ((StyleConstants)key, value);
815 }
816 }
817 }
818 return super.getAttribute(key);
819 }
820 }
821
822 /**
823 * Small set of attributes that does conversion of requests
824 * for attributes of type StyleConstants.
825 */
826 class SmallConversionSet extends SmallAttributeSet {
827
828 /**
829 * Creates a new attribute set based on a supplied set of attributes.
830 *
831 * @param attrs the set of attributes
832 */
833 public SmallConversionSet(AttributeSet attrs) {
834 super(attrs);
835 }
836
837 /**
838 * Checks whether a given attribute is defined.
839 *
840 * @param key the attribute key
841 * @return true if the attribute is defined
842 * @see AttributeSet#isDefined
843 */
844 public boolean isDefined(Object key) {
845 if (key instanceof StyleConstants) {
846 Object cssKey = css.styleConstantsKeyToCSSKey
847 ((StyleConstants)key);
848 if (cssKey != null) {
849 return super.isDefined(cssKey);
850 }
851 }
852 return super.isDefined(key);
853 }
854
855 /**
856 * Gets the value of an attribute.
857 *
858 * @param key the attribute name
859 * @return the attribute value
860 * @see AttributeSet#getAttribute
861 */
862 public Object getAttribute(Object key) {
863 if (key instanceof StyleConstants) {
864 Object cssKey = css.styleConstantsKeyToCSSKey
865 ((StyleConstants)key);
866 if (cssKey != null) {
867 Object value = super.getAttribute(cssKey);
868 if (value != null) {
869 return css.cssValueToStyleConstantsValue
870 ((StyleConstants)key, value);
871 }
872 }
873 }
874 return super.getAttribute(key);
875 }
876 }
877
878 // ---- Resource handling ----------------------------------------
879
880 /**
881 * Fetches the font to use for the given set of attributes.
882 */
883 public Font getFont(AttributeSet a) {
884 return css.getFont(this, a, 12, this);
885 }
886
887 /**
888 * Takes a set of attributes and turn it into a foreground color
889 * specification. This might be used to specify things
890 * like brighter, more hue, etc.
891 *
892 * @param a the set of attributes
893 * @return the color
894 */
895 public Color getForeground(AttributeSet a) {
896 Color c = css.getColor(a, CSS.Attribute.COLOR);
897 if (c == null) {
898 return Color.black;
899 }
900 return c;
901 }
902
903 /**
904 * Takes a set of attributes and turn it into a background color
905 * specification. This might be used to specify things
906 * like brighter, more hue, etc.
907 *
908 * @param a the set of attributes
909 * @return the color
910 */
911 public Color getBackground(AttributeSet a) {
912 return css.getColor(a, CSS.Attribute.BACKGROUND_COLOR);
913 }
914
915 /**
916 * Fetches the box formatter to use for the given set
917 * of CSS attributes.
918 */
919 public BoxPainter getBoxPainter(AttributeSet a) {
920 return new BoxPainter(a, css, this);
921 }
922
923 /**
924 * Fetches the list formatter to use for the given set
925 * of CSS attributes.
926 */
927 public ListPainter getListPainter(AttributeSet a) {
928 return new ListPainter(a, this);
929 }
930
931 /**
932 * Sets the base font size, with valid values between 1 and 7.
933 */
934 public void setBaseFontSize(int sz) {
935 css.setBaseFontSize(sz);
936 }
937
938 /**
939 * Sets the base font size from the passed in String. The string
940 * can either identify a specific font size, with legal values between
941 * 1 and 7, or identifiy a relative font size such as +1 or -2.
942 */
943 public void setBaseFontSize(String size) {
944 css.setBaseFontSize(size);
945 }
946
947 public static int getIndexOfSize(float pt) {
948 return CSS.getIndexOfSize(pt, sizeMapDefault);
949 }
950
951 /**
952 * Returns the point size, given a size index.
953 */
954 public float getPointSize(int index) {
955 return css.getPointSize(index, this);
956 }
957
958 /**
959 * Given a string such as "+2", "-2", or "2",
960 * returns a point size value.
961 */
962 public float getPointSize(String size) {
963 return css.getPointSize(size, this);
964 }
965
966 /**
967 * Converts a color string such as "RED" or "#NNNNNN" to a Color.
968 * Note: This will only convert the HTML3.2 color strings
969 * or a string of length 7;
970 * otherwise, it will return null.
971 */
972 public Color stringToColor(String string) {
973 return CSS.stringToColor(string);
974 }
975
976 /**
977 * Returns the ImageIcon to draw in the background for
978 * <code>attr</code>.
979 */
980 ImageIcon getBackgroundImage(AttributeSet attr) {
981 Object value = attr.getAttribute(CSS.Attribute.BACKGROUND_IMAGE);
982
983 if (value != null) {
984 return ((CSS.BackgroundImage)value).getImage(getBase());
985 }
986 return null;
987 }
988
989 /**
990 * Adds a rule into the StyleSheet.
991 *
992 * @param selector the selector to use for the rule.
993 * This will be a set of simple selectors, and must
994 * be a length of 1 or greater.
995 * @param declaration the set of CSS attributes that
996 * make up the rule.
997 */
998 void addRule(String[] selector, AttributeSet declaration,
999 boolean isLinked) {
1000 int n = selector.length;
1001 StringBuilder sb = new StringBuilder();
1002 sb.append(selector[0]);
1003 for (int counter = 1; counter < n; counter++) {
1004 sb.append(' ');
1005 sb.append(selector[counter]);
1006 }
1007 String selectorName = sb.toString();
1008 Style rule = getStyle(selectorName);
1009 if (rule == null) {
1010 // Notice how the rule is first created, and it not part of
1011 // the synchronized block. It is done like this as creating
1012 // a new rule will fire a ChangeEvent. We do not want to be
1013 // holding the lock when calling to other objects, it can
1014 // result in deadlock.
1015 Style altRule = addStyle(selectorName, null);
1016 synchronized(this) {
1017 SelectorMapping mapping = getRootSelectorMapping();
1018 for (int i = n - 1; i >= 0; i--) {
1019 mapping = mapping.getChildSelectorMapping
1020 (selector[i], true);
1021 }
1022 rule = mapping.getStyle();
1023 if (rule == null) {
1024 rule = altRule;
1025 mapping.setStyle(rule);
1026 refreshResolvedRules(selectorName, selector, rule,
1027 mapping.getSpecificity());
1028 }
1029 }
1030 }
1031 if (isLinked) {
1032 rule = getLinkedStyle(rule);
1033 }
1034 rule.addAttributes(declaration);
1035 }
1036
1037 //
1038 // The following gaggle of methods is used in maintaing the rules from
1039 // the sheet.
1040 //
1041
1042 /**
1043 * Updates the attributes of the rules to reference any related
1044 * rules in <code>ss</code>.
1045 */
1046 private synchronized void linkStyleSheetAt(StyleSheet ss, int index) {
1047 if (resolvedStyles.size() > 0) {
1048 Enumeration<ResolvedStyle> values = resolvedStyles.elements();
1049 while (values.hasMoreElements()) {
1050 ResolvedStyle rule = values.nextElement();
1051 rule.insertExtendedStyleAt(ss.getRule(rule.getName()),
1052 index);
1053 }
1054 }
1055 }
1056
1057 /**
1058 * Removes references to the rules in <code>ss</code>.
1059 * <code>index</code> gives the index the StyleSheet was at, that is
1060 * how many StyleSheets had been added before it.
1061 */
1062 private synchronized void unlinkStyleSheet(StyleSheet ss, int index) {
1063 if (resolvedStyles.size() > 0) {
1064 Enumeration<ResolvedStyle> values = resolvedStyles.elements();
1065 while (values.hasMoreElements()) {
1066 ResolvedStyle rule = values.nextElement();
1067 rule.removeExtendedStyleAt(index);
1068 }
1069 }
1070 }
1071
1072 /**
1073 * Returns the simple selectors that comprise selector.
1074 */
1075 /* protected */
1076 String[] getSimpleSelectors(String selector) {
1077 selector = cleanSelectorString(selector);
1078 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1079 Vector<String> selectors = sb.getVector();
1080 int lastIndex = 0;
1081 int length = selector.length();
1082 while (lastIndex != -1) {
1083 int newIndex = selector.indexOf(' ', lastIndex);
1084 if (newIndex != -1) {
1085 selectors.addElement(selector.substring(lastIndex, newIndex));
1086 if (++newIndex == length) {
1087 lastIndex = -1;
1088 }
1089 else {
1090 lastIndex = newIndex;
1091 }
1092 }
1093 else {
1094 selectors.addElement(selector.substring(lastIndex));
1095 lastIndex = -1;
1096 }
1097 }
1098 String[] retValue = new String[selectors.size()];
1099 selectors.copyInto(retValue);
1100 SearchBuffer.releaseSearchBuffer(sb);
1101 return retValue;
1102 }
1103
1104 /**
1105 * Returns a string that only has one space between simple selectors,
1106 * which may be the passed in String.
1107 */
1108 /*protected*/ String cleanSelectorString(String selector) {
1109 boolean lastWasSpace = true;
1110 for (int counter = 0, maxCounter = selector.length();
1111 counter < maxCounter; counter++) {
1112 switch(selector.charAt(counter)) {
1113 case ' ':
1114 if (lastWasSpace) {
1115 return _cleanSelectorString(selector);
1116 }
1117 lastWasSpace = true;
1118 break;
1119 case '\n':
1120 case '\r':
1121 case '\t':
1122 return _cleanSelectorString(selector);
1123 default:
1124 lastWasSpace = false;
1125 }
1126 }
1127 if (lastWasSpace) {
1128 return _cleanSelectorString(selector);
1129 }
1130 // It was fine.
1131 return selector;
1132 }
1133
1134 /**
1135 * Returns a new String that contains only one space between non
1136 * white space characters.
1137 */
1138 private String _cleanSelectorString(String selector) {
1139 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1140 StringBuffer buff = sb.getStringBuffer();
1141 boolean lastWasSpace = true;
1142 int lastIndex = 0;
1143 char[] chars = selector.toCharArray();
1144 int numChars = chars.length;
1145 String retValue = null;
1146 try {
1147 for (int counter = 0; counter < numChars; counter++) {
1148 switch(chars[counter]) {
1149 case ' ':
1150 if (!lastWasSpace) {
1151 lastWasSpace = true;
1152 if (lastIndex < counter) {
1153 buff.append(chars, lastIndex,
1154 1 + counter - lastIndex);
1155 }
1156 }
1157 lastIndex = counter + 1;
1158 break;
1159 case '\n':
1160 case '\r':
1161 case '\t':
1162 if (!lastWasSpace) {
1163 lastWasSpace = true;
1164 if (lastIndex < counter) {
1165 buff.append(chars, lastIndex,
1166 counter - lastIndex);
1167 buff.append(' ');
1168 }
1169 }
1170 lastIndex = counter + 1;
1171 break;
1172 default:
1173 lastWasSpace = false;
1174 break;
1175 }
1176 }
1177 if (lastWasSpace && buff.length() > 0) {
1178 // Remove last space.
1179 buff.setLength(buff.length() - 1);
1180 }
1181 else if (lastIndex < numChars) {
1182 buff.append(chars, lastIndex, numChars - lastIndex);
1183 }
1184 retValue = buff.toString();
1185 }
1186 finally {
1187 SearchBuffer.releaseSearchBuffer(sb);
1188 }
1189 return retValue;
1190 }
1191
1192 /**
1193 * Returns the root selector mapping that all selectors are relative
1194 * to. This is an inverted graph of the selectors.
1195 */
1196 private SelectorMapping getRootSelectorMapping() {
1197 return selectorMapping;
1198 }
1199
1200 /**
1201 * Returns the specificity of the passed in String. It assumes the
1202 * passed in string doesn't contain junk, that is each selector is
1203 * separated by a space and each selector at most contains one . or one
1204 * #. A simple selector has a weight of 1, an id selector has a weight
1205 * of 100, and a class selector has a weight of 10000.
1206 */
1207 /*protected*/ static int getSpecificity(String selector) {
1208 int specificity = 0;
1209 boolean lastWasSpace = true;
1210
1211 for (int counter = 0, maxCounter = selector.length();
1212 counter < maxCounter; counter++) {
1213 switch(selector.charAt(counter)) {
1214 case '.':
1215 specificity += 100;
1216 break;
1217 case '#':
1218 specificity += 10000;
1219 break;
1220 case ' ':
1221 lastWasSpace = true;
1222 break;
1223 default:
1224 if (lastWasSpace) {
1225 lastWasSpace = false;
1226 specificity += 1;
1227 }
1228 }
1229 }
1230 return specificity;
1231 }
1232
1233 /**
1234 * Returns the style that linked attributes should be added to. This
1235 * will create the style if necessary.
1236 */
1237 private Style getLinkedStyle(Style localStyle) {
1238 // NOTE: This is not synchronized, and the caller of this does
1239 // not synchronize. There is the chance for one of the callers to
1240 // overwrite the existing resolved parent, but it is quite rare.
1241 // The reason this is left like this is because setResolveParent
1242 // will fire a ChangeEvent. It is really, REALLY bad for us to
1243 // hold a lock when calling outside of us, it may cause a deadlock.
1244 Style retStyle = (Style)localStyle.getResolveParent();
1245 if (retStyle == null) {
1246 retStyle = addStyle(null, null);
1247 localStyle.setResolveParent(retStyle);
1248 }
1249 return retStyle;
1250 }
1251
1252 /**
1253 * Returns the resolved style for <code>selector</code>. This will
1254 * create the resolved style, if necessary.
1255 */
1256 private synchronized Style getResolvedStyle(String selector,
1257 Vector elements,
1258 HTML.Tag t) {
1259 Style retStyle = resolvedStyles.get(selector);
1260 if (retStyle == null) {
1261 retStyle = createResolvedStyle(selector, elements, t);
1262 }
1263 return retStyle;
1264 }
1265
1266 /**
1267 * Returns the resolved style for <code>selector</code>. This will
1268 * create the resolved style, if necessary.
1269 */
1270 private synchronized Style getResolvedStyle(String selector) {
1271 Style retStyle = resolvedStyles.get(selector);
1272 if (retStyle == null) {
1273 retStyle = createResolvedStyle(selector);
1274 }
1275 return retStyle;
1276 }
1277
1278 /**
1279 * Adds <code>mapping</code> to <code>elements</code>. It is added
1280 * such that <code>elements</code> will remain ordered by
1281 * specificity.
1282 */
1283 private void addSortedStyle(SelectorMapping mapping, Vector<SelectorMapping> elements) {
1284 int size = elements.size();
1285
1286 if (size > 0) {
1287 int specificity = mapping.getSpecificity();
1288
1289 for (int counter = 0; counter < size; counter++) {
1290 if (specificity >= elements.elementAt(counter).getSpecificity()) {
1291 elements.insertElementAt(mapping, counter);
1292 return;
1293 }
1294 }
1295 }
1296 elements.addElement(mapping);
1297 }
1298
1299 /**
1300 * Adds <code>parentMapping</code> to <code>styles</code>, and
1301 * recursively calls this method if <code>parentMapping</code> has
1302 * any child mappings for any of the Elements in <code>elements</code>.
1303 */
1304 private synchronized void getStyles(SelectorMapping parentMapping,
1305 Vector<SelectorMapping> styles,
1306 String[] tags, String[] ids, String[] classes,
1307 int index, int numElements,
1308 Hashtable<SelectorMapping, SelectorMapping> alreadyChecked) {
1309 // Avoid desending the same mapping twice.
1310 if (alreadyChecked.contains(parentMapping)) {
1311 return;
1312 }
1313 alreadyChecked.put(parentMapping, parentMapping);
1314 Style style = parentMapping.getStyle();
1315 if (style != null) {
1316 addSortedStyle(parentMapping, styles);
1317 }
1318 for (int counter = index; counter < numElements; counter++) {
1319 String tagString = tags[counter];
1320 if (tagString != null) {
1321 SelectorMapping childMapping = parentMapping.
1322 getChildSelectorMapping(tagString, false);
1323 if (childMapping != null) {
1324 getStyles(childMapping, styles, tags, ids, classes,
1325 counter + 1, numElements, alreadyChecked);
1326 }
1327 if (classes[counter] != null) {
1328 String className = classes[counter];
1329 childMapping = parentMapping.getChildSelectorMapping(
1330 tagString + "." + className, false);
1331 if (childMapping != null) {
1332 getStyles(childMapping, styles, tags, ids, classes,
1333 counter + 1, numElements, alreadyChecked);
1334 }
1335 childMapping = parentMapping.getChildSelectorMapping(
1336 "." + className, false);
1337 if (childMapping != null) {
1338 getStyles(childMapping, styles, tags, ids, classes,
1339 counter + 1, numElements, alreadyChecked);
1340 }
1341 }
1342 if (ids[counter] != null) {
1343 String idName = ids[counter];
1344 childMapping = parentMapping.getChildSelectorMapping(
1345 tagString + "#" + idName, false);
1346 if (childMapping != null) {
1347 getStyles(childMapping, styles, tags, ids, classes,
1348 counter + 1, numElements, alreadyChecked);
1349 }
1350 childMapping = parentMapping.getChildSelectorMapping(
1351 "#" + idName, false);
1352 if (childMapping != null) {
1353 getStyles(childMapping, styles, tags, ids, classes,
1354 counter + 1, numElements, alreadyChecked);
1355 }
1356 }
1357 }
1358 }
1359 }
1360
1361 /**
1362 * Creates and returns a Style containing all the rules that match
1363 * <code>selector</code>.
1364 */
1365 private synchronized Style createResolvedStyle(String selector,
1366 String[] tags,
1367 String[] ids, String[] classes) {
1368 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1369 Vector<SelectorMapping> tempVector = sb.getVector();
1370 Hashtable<SelectorMapping, SelectorMapping> tempHashtable = sb.getHashtable();
1371 // Determine all the Styles that are appropriate, placing them
1372 // in tempVector
1373 try {
1374 SelectorMapping mapping = getRootSelectorMapping();
1375 int numElements = tags.length;
1376 String tagString = tags[0];
1377 SelectorMapping childMapping = mapping.getChildSelectorMapping(
1378 tagString, false);
1379 if (childMapping != null) {
1380 getStyles(childMapping, tempVector, tags, ids, classes, 1,
1381 numElements, tempHashtable);
1382 }
1383 if (classes[0] != null) {
1384 String className = classes[0];
1385 childMapping = mapping.getChildSelectorMapping(
1386 tagString + "." + className, false);
1387 if (childMapping != null) {
1388 getStyles(childMapping, tempVector, tags, ids, classes, 1,
1389 numElements, tempHashtable);
1390 }
1391 childMapping = mapping.getChildSelectorMapping(
1392 "." + className, false);
1393 if (childMapping != null) {
1394 getStyles(childMapping, tempVector, tags, ids, classes,
1395 1, numElements, tempHashtable);
1396 }
1397 }
1398 if (ids[0] != null) {
1399 String idName = ids[0];
1400 childMapping = mapping.getChildSelectorMapping(
1401 tagString + "#" + idName, false);
1402 if (childMapping != null) {
1403 getStyles(childMapping, tempVector, tags, ids, classes,
1404 1, numElements, tempHashtable);
1405 }
1406 childMapping = mapping.getChildSelectorMapping(
1407 "#" + idName, false);
1408 if (childMapping != null) {
1409 getStyles(childMapping, tempVector, tags, ids, classes,
1410 1, numElements, tempHashtable);
1411 }
1412 }
1413 // Create a new Style that will delegate to all the matching
1414 // Styles.
1415 int numLinkedSS = (linkedStyleSheets != null) ?
1416 linkedStyleSheets.size() : 0;
1417 int numStyles = tempVector.size();
1418 AttributeSet[] attrs = new AttributeSet[numStyles + numLinkedSS];
1419 for (int counter = 0; counter < numStyles; counter++) {
1420 attrs[counter] = tempVector.elementAt(counter).getStyle();
1421 }
1422 // Get the AttributeSet from linked style sheets.
1423 for (int counter = 0; counter < numLinkedSS; counter++) {
1424 AttributeSet attr = linkedStyleSheets.elementAt(counter).getRule(selector);
1425 if (attr == null) {
1426 attrs[counter + numStyles] = SimpleAttributeSet.EMPTY;
1427 }
1428 else {
1429 attrs[counter + numStyles] = attr;
1430 }
1431 }
1432 ResolvedStyle retStyle = new ResolvedStyle(selector, attrs,
1433 numStyles);
1434 resolvedStyles.put(selector, retStyle);
1435 return retStyle;
1436 }
1437 finally {
1438 SearchBuffer.releaseSearchBuffer(sb);
1439 }
1440 }
1441
1442 /**
1443 * Creates and returns a Style containing all the rules that
1444 * matches <code>selector</code>.
1445 *
1446 * @param elements a Vector of all the Elements
1447 * the style is being asked for. The
1448 * first Element is the deepest Element, with the last Element
1449 * representing the root.
1450 * @param t the Tag to use for
1451 * the first Element in <code>elements</code>
1452 */
1453 private Style createResolvedStyle(String selector, Vector elements,
1454 HTML.Tag t) {
1455 int numElements = elements.size();
1456 // Build three arrays, one for tags, one for class's, and one for
1457 // id's
1458 String tags[] = new String[numElements];
1459 String ids[] = new String[numElements];
1460 String classes[] = new String[numElements];
1461 for (int counter = 0; counter < numElements; counter++) {
1462 Element e = (Element)elements.elementAt(counter);
1463 AttributeSet attr = e.getAttributes();
1464 if (counter == 0 && e.isLeaf()) {
1465 // For leafs, we use the second tier attributes.
1466 Object testAttr = attr.getAttribute(t);
1467 if (testAttr instanceof AttributeSet) {
1468 attr = (AttributeSet)testAttr;
1469 }
1470 else {
1471 attr = null;
1472 }
1473 }
1474 if (attr != null) {
1475 HTML.Tag tag = (HTML.Tag)attr.getAttribute(StyleConstants.
1476 NameAttribute);
1477 if (tag != null) {
1478 tags[counter] = tag.toString();
1479 }
1480 else {
1481 tags[counter] = null;
1482 }
1483 if (attr.isDefined(HTML.Attribute.CLASS)) {
1484 classes[counter] = attr.getAttribute
1485 (HTML.Attribute.CLASS).toString();
1486 }
1487 else {
1488 classes[counter] = null;
1489 }
1490 if (attr.isDefined(HTML.Attribute.ID)) {
1491 ids[counter] = attr.getAttribute(HTML.Attribute.ID).
1492 toString();
1493 }
1494 else {
1495 ids[counter] = null;
1496 }
1497 }
1498 else {
1499 tags[counter] = ids[counter] = classes[counter] = null;
1500 }
1501 }
1502 tags[0] = t.toString();
1503 return createResolvedStyle(selector, tags, ids, classes);
1504 }
1505
1506 /**
1507 * Creates and returns a Style containing all the rules that match
1508 * <code>selector</code>. It is assumed that each simple selector
1509 * in <code>selector</code> is separated by a space.
1510 */
1511 private Style createResolvedStyle(String selector) {
1512 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1513 // Will contain the tags, ids, and classes, in that order.
1514 Vector<String> elements = sb.getVector();
1515 try {
1516 boolean done;
1517 int dotIndex = 0;
1518 int spaceIndex;
1519 int poundIndex = 0;
1520 int lastIndex = 0;
1521 int length = selector.length();
1522 while (lastIndex < length) {
1523 if (dotIndex == lastIndex) {
1524 dotIndex = selector.indexOf('.', lastIndex);
1525 }
1526 if (poundIndex == lastIndex) {
1527 poundIndex = selector.indexOf('#', lastIndex);
1528 }
1529 spaceIndex = selector.indexOf(' ', lastIndex);
1530 if (spaceIndex == -1) {
1531 spaceIndex = length;
1532 }
1533 if (dotIndex != -1 && poundIndex != -1 &&
1534 dotIndex < spaceIndex && poundIndex < spaceIndex) {
1535 if (poundIndex < dotIndex) {
1536 // #.
1537 if (lastIndex == poundIndex) {
1538 elements.addElement("");
1539 }
1540 else {
1541 elements.addElement(selector.substring(lastIndex,
1542 poundIndex));
1543 }
1544 if ((dotIndex + 1) < spaceIndex) {
1545 elements.addElement(selector.substring
1546 (dotIndex + 1, spaceIndex));
1547 }
1548 else {
1549 elements.addElement(null);
1550 }
1551 if ((poundIndex + 1) == dotIndex) {
1552 elements.addElement(null);
1553 }
1554 else {
1555 elements.addElement(selector.substring
1556 (poundIndex + 1, dotIndex));
1557 }
1558 }
1559 else if(poundIndex < spaceIndex) {
1560 // .#
1561 if (lastIndex == dotIndex) {
1562 elements.addElement("");
1563 }
1564 else {
1565 elements.addElement(selector.substring(lastIndex,
1566 dotIndex));
1567 }
1568 if ((dotIndex + 1) < poundIndex) {
1569 elements.addElement(selector.substring
1570 (dotIndex + 1, poundIndex));
1571 }
1572 else {
1573 elements.addElement(null);
1574 }
1575 if ((poundIndex + 1) == spaceIndex) {
1576 elements.addElement(null);
1577 }
1578 else {
1579 elements.addElement(selector.substring
1580 (poundIndex + 1, spaceIndex));
1581 }
1582 }
1583 dotIndex = poundIndex = spaceIndex + 1;
1584 }
1585 else if (dotIndex != -1 && dotIndex < spaceIndex) {
1586 // .
1587 if (dotIndex == lastIndex) {
1588 elements.addElement("");
1589 }
1590 else {
1591 elements.addElement(selector.substring(lastIndex,
1592 dotIndex));
1593 }
1594 if ((dotIndex + 1) == spaceIndex) {
1595 elements.addElement(null);
1596 }
1597 else {
1598 elements.addElement(selector.substring(dotIndex + 1,
1599 spaceIndex));
1600 }
1601 elements.addElement(null);
1602 dotIndex = spaceIndex + 1;
1603 }
1604 else if (poundIndex != -1 && poundIndex < spaceIndex) {
1605 // #
1606 if (poundIndex == lastIndex) {
1607 elements.addElement("");
1608 }
1609 else {
1610 elements.addElement(selector.substring(lastIndex,
1611 poundIndex));
1612 }
1613 elements.addElement(null);
1614 if ((poundIndex + 1) == spaceIndex) {
1615 elements.addElement(null);
1616 }
1617 else {
1618 elements.addElement(selector.substring(poundIndex + 1,
1619 spaceIndex));
1620 }
1621 poundIndex = spaceIndex + 1;
1622 }
1623 else {
1624 // id
1625 elements.addElement(selector.substring(lastIndex,
1626 spaceIndex));
1627 elements.addElement(null);
1628 elements.addElement(null);
1629 }
1630 lastIndex = spaceIndex + 1;
1631 }
1632 // Create the tag, id, and class arrays.
1633 int total = elements.size();
1634 int numTags = total / 3;
1635 String[] tags = new String[numTags];
1636 String[] ids = new String[numTags];
1637 String[] classes = new String[numTags];
1638 for (int index = 0, eIndex = total - 3; index < numTags;
1639 index++, eIndex -= 3) {
1640 tags[index] = elements.elementAt(eIndex);
1641 classes[index] = elements.elementAt(eIndex + 1);
1642 ids[index] = elements.elementAt(eIndex + 2);
1643 }
1644 return createResolvedStyle(selector, tags, ids, classes);
1645 }
1646 finally {
1647 SearchBuffer.releaseSearchBuffer(sb);
1648 }
1649 }
1650
1651 /**
1652 * Should be invoked when a new rule is added that did not previously
1653 * exist. Goes through and refreshes the necessary resolved
1654 * rules.
1655 */
1656 private synchronized void refreshResolvedRules(String selectorName,
1657 String[] selector,
1658 Style newStyle,
1659 int specificity) {
1660 if (resolvedStyles.size() > 0) {
1661 Enumeration<ResolvedStyle> values = resolvedStyles.elements();
1662 while (values.hasMoreElements()) {
1663 ResolvedStyle style = values.nextElement();
1664 if (style.matches(selectorName)) {
1665 style.insertStyle(newStyle, specificity);
1666 }
1667 }
1668 }
1669 }
1670
1671
1672 /**
1673 * A temporary class used to hold a Vector, a StringBuffer and a
1674 * Hashtable. This is used to avoid allocing a lot of garbage when
1675 * searching for rules. Use the static method obtainSearchBuffer and
1676 * releaseSearchBuffer to get a SearchBuffer, and release it when
1677 * done.
1678 */
1679 private static class SearchBuffer {
1680 /** A stack containing instances of SearchBuffer. Used in getting
1681 * rules. */
1682 static Stack<SearchBuffer> searchBuffers = new Stack<SearchBuffer>();
1683 // A set of temporary variables that can be used in whatever way.
1684 Vector vector = null;
1685 StringBuffer stringBuffer = null;
1686 Hashtable hashtable = null;
1687
1688 /**
1689 * Returns an instance of SearchBuffer. Be sure and issue
1690 * a releaseSearchBuffer when done with it.
1691 */
1692 static SearchBuffer obtainSearchBuffer() {
1693 SearchBuffer sb;
1694 try {
1695 if(!searchBuffers.empty()) {
1696 sb = searchBuffers.pop();
1697 } else {
1698 sb = new SearchBuffer();
1699 }
1700 } catch (EmptyStackException ese) {
1701 sb = new SearchBuffer();
1702 }
1703 return sb;
1704 }
1705
1706 /**
1707 * Adds <code>sb</code> to the stack of SearchBuffers that can
1708 * be used.
1709 */
1710 static void releaseSearchBuffer(SearchBuffer sb) {
1711 sb.empty();
1712 searchBuffers.push(sb);
1713 }
1714
1715 StringBuffer getStringBuffer() {
1716 if (stringBuffer == null) {
1717 stringBuffer = new StringBuffer();
1718 }
1719 return stringBuffer;
1720 }
1721
1722 Vector getVector() {
1723 if (vector == null) {
1724 vector = new Vector();
1725 }
1726 return vector;
1727 }
1728
1729 Hashtable getHashtable() {
1730 if (hashtable == null) {
1731 hashtable = new Hashtable();
1732 }
1733 return hashtable;
1734 }
1735
1736 void empty() {
1737 if (stringBuffer != null) {
1738 stringBuffer.setLength(0);
1739 }
1740 if (vector != null) {
1741 vector.removeAllElements();
1742 }
1743 if (hashtable != null) {
1744 hashtable.clear();
1745 }
1746 }
1747 }
1748
1749
1750 static final Border noBorder = new EmptyBorder(0,0,0,0);
1751
1752 /**
1753 * Class to carry out some of the duties of
1754 * CSS formatting. Implementations of this
1755 * class enable views to present the CSS formatting
1756 * while not knowing anything about how the CSS values
1757 * are being cached.
1758 * <p>
1759 * As a delegate of Views, this object is responsible for
1760 * the insets of a View and making sure the background
1761 * is maintained according to the CSS attributes.
1762 */
1763 public static class BoxPainter implements Serializable {
1764
1765 BoxPainter(AttributeSet a, CSS css, StyleSheet ss) {
1766 this.ss = ss;
1767 this.css = css;
1768 border = getBorder(a);
1769 binsets = border.getBorderInsets(null);
1770 topMargin = getLength(CSS.Attribute.MARGIN_TOP, a);
1771 bottomMargin = getLength(CSS.Attribute.MARGIN_BOTTOM, a);
1772 leftMargin = getLength(CSS.Attribute.MARGIN_LEFT, a);
1773 rightMargin = getLength(CSS.Attribute.MARGIN_RIGHT, a);
1774 bg = ss.getBackground(a);
1775 if (ss.getBackgroundImage(a) != null) {
1776 bgPainter = new BackgroundImagePainter(a, css, ss);
1777 }
1778 }
1779
1780 /**
1781 * Fetches a border to render for the given attributes.
1782 * PENDING(prinz) This is pretty badly hacked at the
1783 * moment.
1784 */
1785 Border getBorder(AttributeSet a) {
1786 return new CSSBorder(a);
1787 }
1788
1789 /**
1790 * Fetches the color to use for borders. This will either be
1791 * the value specified by the border-color attribute (which
1792 * is not inherited), or it will default to the color attribute
1793 * (which is inherited).
1794 */
1795 Color getBorderColor(AttributeSet a) {
1796 Color color = css.getColor(a, CSS.Attribute.BORDER_COLOR);
1797 if (color == null) {
1798 color = css.getColor(a, CSS.Attribute.COLOR);
1799 if (color == null) {
1800 return Color.black;
1801 }
1802 }
1803 return color;
1804 }
1805
1806 /**
1807 * Fetches the inset needed on a given side to
1808 * account for the margin, border, and padding.
1809 *
1810 * @param side The size of the box to fetch the
1811 * inset for. This can be View.TOP,
1812 * View.LEFT, View.BOTTOM, or View.RIGHT.
1813 * @param v the view making the request. This is
1814 * used to get the AttributeSet, and may be used to
1815 * resolve percentage arguments.
1816 * @exception IllegalArgumentException for an invalid direction
1817 */
1818 public float getInset(int side, View v) {
1819 AttributeSet a = v.getAttributes();
1820 float inset = 0;
1821 switch(side) {
1822 case View.LEFT:
1823 inset += getOrientationMargin(HorizontalMargin.LEFT,
1824 leftMargin, a, isLeftToRight(v));
1825 inset += binsets.left;
1826 inset += getLength(CSS.Attribute.PADDING_LEFT, a);
1827 break;
1828 case View.RIGHT:
1829 inset += getOrientationMargin(HorizontalMargin.RIGHT,
1830 rightMargin, a, isLeftToRight(v));
1831 inset += binsets.right;
1832 inset += getLength(CSS.Attribute.PADDING_RIGHT, a);
1833 break;
1834 case View.TOP:
1835 inset += topMargin;
1836 inset += binsets.top;
1837 inset += getLength(CSS.Attribute.PADDING_TOP, a);
1838 break;
1839 case View.BOTTOM:
1840 inset += bottomMargin;
1841 inset += binsets.bottom;
1842 inset += getLength(CSS.Attribute.PADDING_BOTTOM, a);
1843 break;
1844 default:
1845 throw new IllegalArgumentException("Invalid side: " + side);
1846 }
1847 return inset;
1848 }
1849
1850 /**
1851 * Paints the CSS box according to the attributes
1852 * given. This should paint the border, padding,
1853 * and background.
1854 *
1855 * @param g the rendering surface.
1856 * @param x the x coordinate of the allocated area to
1857 * render into.
1858 * @param y the y coordinate of the allocated area to
1859 * render into.
1860 * @param w the width of the allocated area to render into.
1861 * @param h the height of the allocated area to render into.
1862 * @param v the view making the request. This is
1863 * used to get the AttributeSet, and may be used to
1864 * resolve percentage arguments.
1865 */
1866 public void paint(Graphics g, float x, float y, float w, float h, View v) {
1867 // PENDING(prinz) implement real rendering... which would
1868 // do full set of border and background capabilities.
1869 // remove margin
1870
1871 float dx = 0;
1872 float dy = 0;
1873 float dw = 0;
1874 float dh = 0;
1875 AttributeSet a = v.getAttributes();
1876 boolean isLeftToRight = isLeftToRight(v);
1877 float localLeftMargin = getOrientationMargin(HorizontalMargin.LEFT,
1878 leftMargin,
1879 a, isLeftToRight);
1880 float localRightMargin = getOrientationMargin(HorizontalMargin.RIGHT,
1881 rightMargin,
1882 a, isLeftToRight);
1883 if (!(v instanceof HTMLEditorKit.HTMLFactory.BodyBlockView)) {
1884 dx = localLeftMargin;
1885 dy = topMargin;
1886 dw = -(localLeftMargin + localRightMargin);
1887 dh = -(topMargin + bottomMargin);
1888 }
1889 if (bg != null) {
1890 g.setColor(bg);
1891 g.fillRect((int) (x + dx),
1892 (int) (y + dy),
1893 (int) (w + dw),
1894 (int) (h + dh));
1895 }
1896 if (bgPainter != null) {
1897 bgPainter.paint(g, x + dx, y + dy, w + dw, h + dh, v);
1898 }
1899 x += localLeftMargin;
1900 y += topMargin;
1901 w -= localLeftMargin + localRightMargin;
1902 h -= topMargin + bottomMargin;
1903 if (border instanceof BevelBorder) {
1904 //BevelBorder does not support border width
1905 int bw = (int) getLength(CSS.Attribute.BORDER_TOP_WIDTH, a);
1906 for (int i = bw - 1; i >= 0; i--) {
1907 border.paintBorder(null, g, (int) x + i, (int) y + i,
1908 (int) w - 2 * i, (int) h - 2 * i);
1909 }
1910 } else {
1911 border.paintBorder(null, g, (int) x, (int) y, (int) w, (int) h);
1912 }
1913 }
1914
1915 float getLength(CSS.Attribute key, AttributeSet a) {
1916 return css.getLength(a, key, ss);
1917 }
1918
1919 static boolean isLeftToRight(View v) {
1920 boolean ret = true;
1921 if (isOrientationAware(v)) {
1922 Container container;
1923 if (v != null && (container = v.getContainer()) != null) {
1924 ret = container.getComponentOrientation().isLeftToRight();
1925 }
1926 }
1927 return ret;
1928 }
1929
1930 /*
1931 * only certain tags are concerned about orientation
1932 * <dir>, <menu>, <ul>, <ol>
1933 * for all others we return true. It is implemented this way
1934 * for performance purposes
1935 */
1936 static boolean isOrientationAware(View v) {
1937 boolean ret = false;
1938 AttributeSet attr;
1939 Object obj;
1940 if (v != null
1941 && (attr = v.getElement().getAttributes()) != null
1942 && (obj = attr.getAttribute(StyleConstants.NameAttribute)) instanceof HTML.Tag
1943 && (obj == HTML.Tag.DIR
1944 || obj == HTML.Tag.MENU
1945 || obj == HTML.Tag.UL
1946 || obj == HTML.Tag.OL)) {
1947 ret = true;
1948 }
1949
1950 return ret;
1951 }
1952
1953 static enum HorizontalMargin { LEFT, RIGHT }
1954
1955 /**
1956 * for <dir>, <menu>, <ul> etc.
1957 * margins are Left-To-Right/Right-To-Left depended.
1958 * see 5088268 for more details
1959 * margin-(left|right)-(ltr|rtl) were introduced to describe it
1960 * if margin-(left|right) is present we are to use it.
1961 *
1962 * @param side The horizontal side to fetch margin for
1963 * This can be HorizontalMargin.LEFT or HorizontalMargin.RIGHT
1964 * @param cssMargin margin from css
1965 * @param a AttributeSet for the View we getting margin for
1966 * @param isLeftToRight
1967 * @return orientation depended margin
1968 */
1969 float getOrientationMargin(HorizontalMargin side, float cssMargin,
1970 AttributeSet a, boolean isLeftToRight) {
1971 float margin = cssMargin;
1972 float orientationMargin = cssMargin;
1973 Object cssMarginValue = null;
1974 switch (side) {
1975 case RIGHT:
1976 {
1977 orientationMargin = (isLeftToRight) ?
1978 getLength(CSS.Attribute.MARGIN_RIGHT_LTR, a) :
1979 getLength(CSS.Attribute.MARGIN_RIGHT_RTL, a);
1980 cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_RIGHT);
1981 }
1982 break;
1983 case LEFT :
1984 {
1985 orientationMargin = (isLeftToRight) ?
1986 getLength(CSS.Attribute.MARGIN_LEFT_LTR, a) :
1987 getLength(CSS.Attribute.MARGIN_LEFT_RTL, a);
1988 cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_LEFT);
1989 }
1990 break;
1991 }
1992
1993 if (cssMarginValue == null
1994 && orientationMargin != Integer.MIN_VALUE) {
1995 margin = orientationMargin;
1996 }
1997 return margin;
1998 }
1999
2000 float topMargin;
2001 float bottomMargin;
2002 float leftMargin;
2003 float rightMargin;
2004 // Bitmask, used to indicate what margins are relative:
2005 // bit 0 for top, 1 for bottom, 2 for left and 3 for right.
2006 short marginFlags;
2007 Border border;
2008 Insets binsets;
2009 CSS css;
2010 StyleSheet ss;
2011 Color bg;
2012 BackgroundImagePainter bgPainter;
2013 }
2014
2015 /**
2016 * Class to carry out some of the duties of CSS list
2017 * formatting. Implementations of this
2018 * class enable views to present the CSS formatting
2019 * while not knowing anything about how the CSS values
2020 * are being cached.
2021 */
2022 public static class ListPainter implements Serializable {
2023
2024 ListPainter(AttributeSet attr, StyleSheet ss) {
2025 this.ss = ss;
2026 /* Get the image to use as a list bullet */
2027 String imgstr = (String)attr.getAttribute(CSS.Attribute.
2028 LIST_STYLE_IMAGE);
2029 type = null;
2030 if (imgstr != null && !imgstr.equals("none")) {
2031 String tmpstr = null;
2032 try {
2033 StringTokenizer st = new StringTokenizer(imgstr, "()");
2034 if (st.hasMoreTokens())
2035 tmpstr = st.nextToken();
2036 if (st.hasMoreTokens())
2037 tmpstr = st.nextToken();
2038 URL u = new URL(tmpstr);
2039 img = new ImageIcon(u);
2040 } catch (MalformedURLException e) {
2041 if (tmpstr != null && ss != null && ss.getBase() != null) {
2042 try {
2043 URL u = new URL(ss.getBase(), tmpstr);
2044 img = new ImageIcon(u);
2045 } catch (MalformedURLException murle) {
2046 img = null;
2047 }
2048 }
2049 else {
2050 img = null;
2051 }
2052 }
2053 }
2054
2055 /* Get the type of bullet to use in the list */
2056 if (img == null) {
2057 type = (CSS.Value)attr.getAttribute(CSS.Attribute.
2058 LIST_STYLE_TYPE);
2059 }
2060 start = 1;
2061
2062 paintRect = new Rectangle();
2063 }
2064
2065 /**
2066 * Returns a string that represents the value
2067 * of the HTML.Attribute.TYPE attribute.
2068 * If this attributes is not defined, then
2069 * then the type defaults to "disc" unless
2070 * the tag is on Ordered list. In the case
2071 * of the latter, the default type is "decimal".
2072 */
2073 private CSS.Value getChildType(View childView) {
2074 CSS.Value childtype = (CSS.Value)childView.getAttributes().
2075 getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
2076
2077 if (childtype == null) {
2078 if (type == null) {
2079 // Parent view.
2080 View v = childView.getParent();
2081 HTMLDocument doc = (HTMLDocument)v.getDocument();
2082 if (doc.matchNameAttribute(v.getElement().getAttributes(),
2083 HTML.Tag.OL)) {
2084 childtype = CSS.Value.DECIMAL;
2085 } else {
2086 childtype = CSS.Value.DISC;
2087 }
2088 } else {
2089 childtype = type;
2090 }
2091 }
2092 return childtype;
2093 }
2094
2095 /**
2096 * Obtains the starting index from <code>parent</code>.
2097 */
2098 private void getStart(View parent) {
2099 checkedForStart = true;
2100 Element element = parent.getElement();
2101 if (element != null) {
2102 AttributeSet attr = element.getAttributes();
2103 Object startValue;
2104 if (attr != null && attr.isDefined(HTML.Attribute.START) &&
2105 (startValue = attr.getAttribute
2106 (HTML.Attribute.START)) != null &&
2107 (startValue instanceof String)) {
2108
2109 try {
2110 start = Integer.parseInt((String)startValue);
2111 }
2112 catch (NumberFormatException nfe) {}
2113 }
2114 }
2115 }
2116
2117 /**
2118 * Returns an integer that should be used to render the child at
2119 * <code>childIndex</code> with. The retValue will usually be
2120 * <code>childIndex</code> + 1, unless <code>parentView</code>
2121 * has some Views that do not represent LI's, or one of the views
2122 * has a HTML.Attribute.START specified.
2123 */
2124 private int getRenderIndex(View parentView, int childIndex) {
2125 if (!checkedForStart) {
2126 getStart(parentView);
2127 }
2128 int retIndex = childIndex;
2129 for (int counter = childIndex; counter >= 0; counter--) {
2130 AttributeSet as = parentView.getElement().getElement(counter).
2131 getAttributes();
2132 if (as.getAttribute(StyleConstants.NameAttribute) !=
2133 HTML.Tag.LI) {
2134 retIndex--;
2135 } else if (as.isDefined(HTML.Attribute.VALUE)) {
2136 Object value = as.getAttribute(HTML.Attribute.VALUE);
2137 if (value != null &&
2138 (value instanceof String)) {
2139 try {
2140 int iValue = Integer.parseInt((String)value);
2141 return retIndex - counter + iValue;
2142 }
2143 catch (NumberFormatException nfe) {}
2144 }
2145 }
2146 }
2147 return retIndex + start;
2148 }
2149
2150 /**
2151 * Paints the CSS list decoration according to the
2152 * attributes given.
2153 *
2154 * @param g the rendering surface.
2155 * @param x the x coordinate of the list item allocation
2156 * @param y the y coordinate of the list item allocation
2157 * @param w the width of the list item allocation
2158 * @param h the height of the list item allocation
2159 * @param v the allocated area to paint into.
2160 * @param item which list item is being painted. This
2161 * is a number greater than or equal to 0.
2162 */
2163 public void paint(Graphics g, float x, float y, float w, float h, View v, int item) {
2164 View cv = v.getView(item);
2165 Container host = v.getContainer();
2166 Object name = cv.getElement().getAttributes().getAttribute
2167 (StyleConstants.NameAttribute);
2168 // Only draw something if the View is a list item. This won't
2169 // be the case for comments.
2170 if (!(name instanceof HTML.Tag) ||
2171 name != HTML.Tag.LI) {
2172 return;
2173 }
2174 // deside on what side draw bullets, etc.
2175 isLeftToRight =
2176 host.getComponentOrientation().isLeftToRight();
2177
2178 // How the list indicator is aligned is not specified, it is
2179 // left up to the UA. IE and NS differ on this behavior.
2180 // This is closer to NS where we align to the first line of text.
2181 // If the child is not text we draw the indicator at the
2182 // origin (0).
2183 float align = 0;
2184 if (cv.getViewCount() > 0) {
2185 View pView = cv.getView(0);
2186 Object cName = pView.getElement().getAttributes().
2187 getAttribute(StyleConstants.NameAttribute);
2188 if ((cName == HTML.Tag.P || cName == HTML.Tag.IMPLIED) &&
2189 pView.getViewCount() > 0) {
2190 paintRect.setBounds((int)x, (int)y, (int)w, (int)h);
2191 Shape shape = cv.getChildAllocation(0, paintRect);
2192 if (shape != null && (shape = pView.getView(0).
2193 getChildAllocation(0, shape)) != null) {
2194 Rectangle rect = (shape instanceof Rectangle) ?
2195 (Rectangle)shape : shape.getBounds();
2196
2197 align = pView.getView(0).getAlignment(View.Y_AXIS);
2198 y = rect.y;
2199 h = rect.height;
2200 }
2201 }
2202 }
2203
2204 // set the color of a decoration
2205 Color c = (host.isEnabled()
2206 ? (ss != null
2207 ? ss.getForeground(cv.getAttributes())
2208 : host.getForeground())
2209 : UIManager.getColor("textInactiveText"));
2210 g.setColor(c);
2211
2212 if (img != null) {
2213 drawIcon(g, (int) x, (int) y, (int) w, (int) h, align, host);
2214 return;
2215 }
2216 CSS.Value childtype = getChildType(cv);
2217 Font font = ((StyledDocument)cv.getDocument()).
2218 getFont(cv.getAttributes());
2219 if (font != null) {
2220 g.setFont(font);
2221 }
2222 if (childtype == CSS.Value.SQUARE || childtype == CSS.Value.CIRCLE
2223 || childtype == CSS.Value.DISC) {
2224 drawShape(g, childtype, (int) x, (int) y,
2225 (int) w, (int) h, align);
2226 } else if (childtype == CSS.Value.DECIMAL) {
2227 drawLetter(g, '1', (int) x, (int) y, (int) w, (int) h, align,
2228 getRenderIndex(v, item));
2229 } else if (childtype == CSS.Value.LOWER_ALPHA) {
2230 drawLetter(g, 'a', (int) x, (int) y, (int) w, (int) h, align,
2231 getRenderIndex(v, item));
2232 } else if (childtype == CSS.Value.UPPER_ALPHA) {
2233 drawLetter(g, 'A', (int) x, (int) y, (int) w, (int) h, align,
2234 getRenderIndex(v, item));
2235 } else if (childtype == CSS.Value.LOWER_ROMAN) {
2236 drawLetter(g, 'i', (int) x, (int) y, (int) w, (int) h, align,
2237 getRenderIndex(v, item));
2238 } else if (childtype == CSS.Value.UPPER_ROMAN) {
2239 drawLetter(g, 'I', (int) x, (int) y, (int) w, (int) h, align,
2240 getRenderIndex(v, item));
2241 }
2242 }
2243
2244 /**
2245 * Draws the bullet icon specified by the list-style-image argument.
2246 *
2247 * @param g the graphics context
2248 * @param ax x coordinate to place the bullet
2249 * @param ay y coordinate to place the bullet
2250 * @param aw width of the container the bullet is placed in
2251 * @param ah height of the container the bullet is placed in
2252 * @param align preferred alignment factor for the child view
2253 */
2254 void drawIcon(Graphics g, int ax, int ay, int aw, int ah,
2255 float align, Component c) {
2256 // Align to bottom of icon.
2257 int gap = isLeftToRight ? - (img.getIconWidth() + bulletgap) :
2258 (aw + bulletgap);
2259 int x = ax + gap;
2260 int y = Math.max(ay, ay + (int)(align * ah) -img.getIconHeight());
2261
2262 img.paintIcon(c, g, x, y);
2263 }
2264
2265 /**
2266 * Draws the graphical bullet item specified by the type argument.
2267 *
2268 * @param g the graphics context
2269 * @param type type of bullet to draw (circle, square, disc)
2270 * @param ax x coordinate to place the bullet
2271 * @param ay y coordinate to place the bullet
2272 * @param aw width of the container the bullet is placed in
2273 * @param ah height of the container the bullet is placed in
2274 * @param align preferred alignment factor for the child view
2275 */
2276 void drawShape(Graphics g, CSS.Value type, int ax, int ay, int aw,
2277 int ah, float align) {
2278 // Align to bottom of shape.
2279 int gap = isLeftToRight ? - (bulletgap + 8) : (aw + bulletgap);
2280 int x = ax + gap;
2281 int y = Math.max(ay, ay + (int)(align * ah) - 8);
2282
2283 if (type == CSS.Value.SQUARE) {
2284 g.drawRect(x, y, 8, 8);
2285 } else if (type == CSS.Value.CIRCLE) {
2286 g.drawOval(x, y, 8, 8);
2287 } else {
2288 g.fillOval(x, y, 8, 8);
2289 }
2290 }
2291
2292 /**
2293 * Draws the letter or number for an ordered list.
2294 *
2295 * @param g the graphics context
2296 * @param letter type of ordered list to draw
2297 * @param ax x coordinate to place the bullet
2298 * @param ay y coordinate to place the bullet
2299 * @param aw width of the container the bullet is placed in
2300 * @param ah height of the container the bullet is placed in
2301 * @param index position of the list item in the list
2302 */
2303 void drawLetter(Graphics g, char letter, int ax, int ay, int aw,
2304 int ah, float align, int index) {
2305 String str = formatItemNum(index, letter);
2306 str = isLeftToRight ? str + "." : "." + str;
2307 FontMetrics fm = SwingUtilities2.getFontMetrics(null, g);
2308 int stringwidth = SwingUtilities2.stringWidth(null, fm, str);
2309 int gap = isLeftToRight ? - (stringwidth + bulletgap) :
2310 (aw + bulletgap);
2311 int x = ax + gap;
2312 int y = Math.max(ay + fm.getAscent(), ay + (int)(ah * align));
2313 SwingUtilities2.drawString(null, g, str, x, y);
2314 }
2315
2316 /**
2317 * Converts the item number into the ordered list number
2318 * (i.e. 1 2 3, i ii iii, a b c, etc.
2319 *
2320 * @param itemNum number to format
2321 * @param type type of ordered list
2322 */
2323 String formatItemNum(int itemNum, char type) {
2324 String numStyle = "1";
2325
2326 boolean uppercase = false;
2327
2328 String formattedNum;
2329
2330 switch (type) {
2331 case '1':
2332 default:
2333 formattedNum = String.valueOf(itemNum);
2334 break;
2335
2336 case 'A':
2337 uppercase = true;
2338 // fall through
2339 case 'a':
2340 formattedNum = formatAlphaNumerals(itemNum);
2341 break;
2342
2343 case 'I':
2344 uppercase = true;
2345 // fall through
2346 case 'i':
2347 formattedNum = formatRomanNumerals(itemNum);
2348 }
2349
2350 if (uppercase) {
2351 formattedNum = formattedNum.toUpperCase();
2352 }
2353
2354 return formattedNum;
2355 }
2356
2357 /**
2358 * Converts the item number into an alphabetic character
2359 *
2360 * @param itemNum number to format
2361 */
2362 String formatAlphaNumerals(int itemNum) {
2363 String result;
2364
2365 if (itemNum > 26) {
2366 result = formatAlphaNumerals(itemNum / 26) +
2367 formatAlphaNumerals(itemNum % 26);
2368 } else {
2369 // -1 because item is 1 based.
2370 result = String.valueOf((char)('a' + itemNum - 1));
2371 }
2372
2373 return result;
2374 }
2375
2376 /* list of roman numerals */
2377 static final char romanChars[][] = {
2378 {'i', 'v'},
2379 {'x', 'l' },
2380 {'c', 'd' },
2381 {'m', '?' },
2382 };
2383
2384 /**
2385 * Converts the item number into a roman numeral
2386 *
2387 * @param num number to format
2388 */
2389 String formatRomanNumerals(int num) {
2390 return formatRomanNumerals(0, num);
2391 }
2392
2393 /**
2394 * Converts the item number into a roman numeral
2395 *
2396 * @param num number to format
2397 */
2398 String formatRomanNumerals(int level, int num) {
2399 if (num < 10) {
2400 return formatRomanDigit(level, num);
2401 } else {
2402 return formatRomanNumerals(level + 1, num / 10) +
2403 formatRomanDigit(level, num % 10);
2404 }
2405 }
2406
2407
2408 /**
2409 * Converts the item number into a roman numeral
2410 *
2411 * @param level position
2412 * @param digit digit to format
2413 */
2414 String formatRomanDigit(int level, int digit) {
2415 String result = "";
2416 if (digit == 9) {
2417 result = result + romanChars[level][0];
2418 result = result + romanChars[level + 1][0];
2419 return result;
2420 } else if (digit == 4) {
2421 result = result + romanChars[level][0];
2422 result = result + romanChars[level][1];
2423 return result;
2424 } else if (digit >= 5) {
2425 result = result + romanChars[level][1];
2426 digit -= 5;
2427 }
2428
2429 for (int i = 0; i < digit; i++) {
2430 result = result + romanChars[level][0];
2431 }
2432
2433 return result;
2434 }
2435
2436 private Rectangle paintRect;
2437 private boolean checkedForStart;
2438 private int start;
2439 private CSS.Value type;
2440 URL imageurl;
2441 private StyleSheet ss = null;
2442 Icon img = null;
2443 private int bulletgap = 5;
2444 private boolean isLeftToRight;
2445 }
2446
2447
2448 /**
2449 * Paints the background image.
2450 */
2451 static class BackgroundImagePainter implements Serializable {
2452 ImageIcon backgroundImage;
2453 float hPosition;
2454 float vPosition;
2455 // bit mask: 0 for repeat x, 1 for repeat y, 2 for horiz relative,
2456 // 3 for vert relative
2457 short flags;
2458 // These are used when painting, updatePaintCoordinates updates them.
2459 private int paintX;
2460 private int paintY;
2461 private int paintMaxX;
2462 private int paintMaxY;
2463
2464 BackgroundImagePainter(AttributeSet a, CSS css, StyleSheet ss) {
2465 backgroundImage = ss.getBackgroundImage(a);
2466 // Determine the position.
2467 CSS.BackgroundPosition pos = (CSS.BackgroundPosition)a.getAttribute
2468 (CSS.Attribute.BACKGROUND_POSITION);
2469 if (pos != null) {
2470 hPosition = pos.getHorizontalPosition();
2471 vPosition = pos.getVerticalPosition();
2472 if (pos.isHorizontalPositionRelativeToSize()) {
2473 flags |= 4;
2474 }
2475 else if (pos.isHorizontalPositionRelativeToSize()) {
2476 hPosition *= css.getFontSize(a, 12, ss);
2477 }
2478 if (pos.isVerticalPositionRelativeToSize()) {
2479 flags |= 8;
2480 }
2481 else if (pos.isVerticalPositionRelativeToFontSize()) {
2482 vPosition *= css.getFontSize(a, 12, ss);
2483 }
2484 }
2485 // Determine any repeating values.
2486 CSS.Value repeats = (CSS.Value)a.getAttribute(CSS.Attribute.
2487 BACKGROUND_REPEAT);
2488 if (repeats == null || repeats == CSS.Value.BACKGROUND_REPEAT) {
2489 flags |= 3;
2490 }
2491 else if (repeats == CSS.Value.BACKGROUND_REPEAT_X) {
2492 flags |= 1;
2493 }
2494 else if (repeats == CSS.Value.BACKGROUND_REPEAT_Y) {
2495 flags |= 2;
2496 }
2497 }
2498
2499 void paint(Graphics g, float x, float y, float w, float h, View v) {
2500 Rectangle clip = g.getClipRect();
2501 if (clip != null) {
2502 // Constrain the clip so that images don't draw outside the
2503 // legal bounds.
2504 g.clipRect((int)x, (int)y, (int)w, (int)h);
2505 }
2506 if ((flags & 3) == 0) {
2507 // no repeating
2508 int width = backgroundImage.getIconWidth();
2509 int height = backgroundImage.getIconWidth();
2510 if ((flags & 4) == 4) {
2511 paintX = (int)(x + w * hPosition -
2512 (float)width * hPosition);
2513 }
2514 else {
2515 paintX = (int)x + (int)hPosition;
2516 }
2517 if ((flags & 8) == 8) {
2518 paintY = (int)(y + h * vPosition -
2519 (float)height * vPosition);
2520 }
2521 else {
2522 paintY = (int)y + (int)vPosition;
2523 }
2524 if (clip == null ||
2525 !((paintX + width <= clip.x) ||
2526 (paintY + height <= clip.y) ||
2527 (paintX >= clip.x + clip.width) ||
2528 (paintY >= clip.y + clip.height))) {
2529 backgroundImage.paintIcon(null, g, paintX, paintY);
2530 }
2531 }
2532 else {
2533 int width = backgroundImage.getIconWidth();
2534 int height = backgroundImage.getIconHeight();
2535 if (width > 0 && height > 0) {
2536 paintX = (int)x;
2537 paintY = (int)y;
2538 paintMaxX = (int)(x + w);
2539 paintMaxY = (int)(y + h);
2540 if (updatePaintCoordinates(clip, width, height)) {
2541 while (paintX < paintMaxX) {
2542 int ySpot = paintY;
2543 while (ySpot < paintMaxY) {
2544 backgroundImage.paintIcon(null, g, paintX,
2545 ySpot);
2546 ySpot += height;
2547 }
2548 paintX += width;
2549 }
2550 }
2551 }
2552 }
2553 if (clip != null) {
2554 // Reset clip.
2555 g.setClip(clip.x, clip.y, clip.width, clip.height);
2556 }
2557 }
2558
2559 private boolean updatePaintCoordinates
2560 (Rectangle clip, int width, int height){
2561 if ((flags & 3) == 1) {
2562 paintMaxY = paintY + 1;
2563 }
2564 else if ((flags & 3) == 2) {
2565 paintMaxX = paintX + 1;
2566 }
2567 if (clip != null) {
2568 if ((flags & 3) == 1 && ((paintY + height <= clip.y) ||
2569 (paintY > clip.y + clip.height))) {
2570 // not visible.
2571 return false;
2572 }
2573 if ((flags & 3) == 2 && ((paintX + width <= clip.x) ||
2574 (paintX > clip.x + clip.width))) {
2575 // not visible.
2576 return false;
2577 }
2578 if ((flags & 1) == 1) {
2579 if ((clip.x + clip.width) < paintMaxX) {
2580 if ((clip.x + clip.width - paintX) % width == 0) {
2581 paintMaxX = clip.x + clip.width;
2582 }
2583 else {
2584 paintMaxX = ((clip.x + clip.width - paintX) /
2585 width + 1) * width + paintX;
2586 }
2587 }
2588 if (clip.x > paintX) {
2589 paintX = (clip.x - paintX) / width * width + paintX;
2590 }
2591 }
2592 if ((flags & 2) == 2) {
2593 if ((clip.y + clip.height) < paintMaxY) {
2594 if ((clip.y + clip.height - paintY) % height == 0) {
2595 paintMaxY = clip.y + clip.height;
2596 }
2597 else {
2598 paintMaxY = ((clip.y + clip.height - paintY) /
2599 height + 1) * height + paintY;
2600 }
2601 }
2602 if (clip.y > paintY) {
2603 paintY = (clip.y - paintY) / height * height + paintY;
2604 }
2605 }
2606 }
2607 // Valid
2608 return true;
2609 }
2610 }
2611
2612
2613 /**
2614 * A subclass of MuxingAttributeSet that translates between
2615 * CSS and HTML and StyleConstants. The AttributeSets used are
2616 * the CSS rules that match the Views Elements.
2617 */
2618 class ViewAttributeSet extends MuxingAttributeSet {
2619 ViewAttributeSet(View v) {
2620 host = v;
2621
2622 // PENDING(prinz) fix this up to be a more realistic
2623 // implementation.
2624 Document doc = v.getDocument();
2625 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
2626 Vector<AttributeSet> muxList = sb.getVector();
2627 try {
2628 if (doc instanceof HTMLDocument) {
2629 StyleSheet styles = StyleSheet.this;
2630 Element elem = v.getElement();
2631 AttributeSet a = elem.getAttributes();
2632 AttributeSet htmlAttr = styles.translateHTMLToCSS(a);
2633
2634 if (htmlAttr.getAttributeCount() != 0) {
2635 muxList.addElement(htmlAttr);
2636 }
2637 if (elem.isLeaf()) {
2638 Enumeration keys = a.getAttributeNames();
2639 while (keys.hasMoreElements()) {
2640 Object key = keys.nextElement();
2641 if (key instanceof HTML.Tag) {
2642 if (key == HTML.Tag.A) {
2643 Object o = a.getAttribute(key);
2644 /**
2645 In the case of an A tag, the css rules
2646 apply only for tags that have their
2647 href attribute defined and not for
2648 anchors that only have their name attributes
2649 defined, i.e anchors that function as
2650 destinations. Hence we do not add the
2651 attributes for that latter kind of
2652 anchors. When CSS2 support is added,
2653 it will be possible to specificity this
2654 kind of conditional behaviour in the
2655 stylesheet.
2656 **/
2657 if (o != null && o instanceof AttributeSet) {
2658 AttributeSet attr = (AttributeSet)o;
2659 if (attr.getAttribute(HTML.Attribute.HREF) == null) {
2660 continue;
2661 }
2662 }
2663 }
2664 AttributeSet cssRule = styles.getRule((HTML.Tag) key, elem);
2665 if (cssRule != null) {
2666 muxList.addElement(cssRule);
2667 }
2668 }
2669 }
2670 } else {
2671 HTML.Tag t = (HTML.Tag) a.getAttribute
2672 (StyleConstants.NameAttribute);
2673 AttributeSet cssRule = styles.getRule(t, elem);
2674 if (cssRule != null) {
2675 muxList.addElement(cssRule);
2676 }
2677 }
2678 }
2679 AttributeSet[] attrs = new AttributeSet[muxList.size()];
2680 muxList.copyInto(attrs);
2681 setAttributes(attrs);
2682 }
2683 finally {
2684 SearchBuffer.releaseSearchBuffer(sb);
2685 }
2686 }
2687
2688 // --- AttributeSet methods ----------------------------
2689
2690 /**
2691 * Checks whether a given attribute is defined.
2692 * This will convert the key over to CSS if the
2693 * key is a StyleConstants key that has a CSS
2694 * mapping.
2695 *
2696 * @param key the attribute key
2697 * @return true if the attribute is defined
2698 * @see AttributeSet#isDefined
2699 */
2700 public boolean isDefined(Object key) {
2701 if (key instanceof StyleConstants) {
2702 Object cssKey = css.styleConstantsKeyToCSSKey
2703 ((StyleConstants)key);
2704 if (cssKey != null) {
2705 key = cssKey;
2706 }
2707 }
2708 return super.isDefined(key);
2709 }
2710
2711 /**
2712 * Gets the value of an attribute. If the requested
2713 * attribute is a StyleConstants attribute that has
2714 * a CSS mapping, the request will be converted.
2715 *
2716 * @param key the attribute name
2717 * @return the attribute value
2718 * @see AttributeSet#getAttribute
2719 */
2720 public Object getAttribute(Object key) {
2721 if (key instanceof StyleConstants) {
2722 Object cssKey = css.styleConstantsKeyToCSSKey
2723 ((StyleConstants)key);
2724 if (cssKey != null) {
2725 Object value = doGetAttribute(cssKey);
2726 if (value instanceof CSS.CssValue) {
2727 return ((CSS.CssValue)value).toStyleConstants
2728 ((StyleConstants)key, host);
2729 }
2730 }
2731 }
2732 return doGetAttribute(key);
2733 }
2734
2735 Object doGetAttribute(Object key) {
2736 Object retValue = super.getAttribute(key);
2737 if (retValue != null) {
2738 return retValue;
2739 }
2740 // didn't find it... try parent if it's a css attribute
2741 // that is inherited.
2742 if (key instanceof CSS.Attribute) {
2743 CSS.Attribute css = (CSS.Attribute) key;
2744 if (css.isInherited()) {
2745 AttributeSet parent = getResolveParent();
2746 if (parent != null)
2747 return parent.getAttribute(key);
2748 }
2749 }
2750 return null;
2751 }
2752
2753 /**
2754 * If not overriden, the resolving parent defaults to
2755 * the parent element.
2756 *
2757 * @return the attributes from the parent
2758 * @see AttributeSet#getResolveParent
2759 */
2760 public AttributeSet getResolveParent() {
2761 if (host == null) {
2762 return null;
2763 }
2764 View parent = host.getParent();
2765 return (parent != null) ? parent.getAttributes() : null;
2766 }
2767
2768 /** View created for. */
2769 View host;
2770 }
2771
2772
2773 /**
2774 * A subclass of MuxingAttributeSet that implements Style. Currently
2775 * the MutableAttributeSet methods are unimplemented, that is they
2776 * do nothing.
2777 */
2778 // PENDING(sky): Decide what to do with this. Either make it
2779 // contain a SimpleAttributeSet that modify methods are delegated to,
2780 // or change getRule to return an AttributeSet and then don't make this
2781 // implement Style.
2782 static class ResolvedStyle extends MuxingAttributeSet implements
2783 Serializable, Style {
2784 ResolvedStyle(String name, AttributeSet[] attrs, int extendedIndex) {
2785 super(attrs);
2786 this.name = name;
2787 this.extendedIndex = extendedIndex;
2788 }
2789
2790 /**
2791 * Inserts a Style into the receiver so that the styles the
2792 * receiver represents are still ordered by specificity.
2793 * <code>style</code> will be added before any extended styles, that
2794 * is before extendedIndex.
2795 */
2796 synchronized void insertStyle(Style style, int specificity) {
2797 AttributeSet[] attrs = getAttributes();
2798 int maxCounter = attrs.length;
2799 int counter = 0;
2800 for (;counter < extendedIndex; counter++) {
2801 if (specificity > getSpecificity(((Style)attrs[counter]).
2802 getName())) {
2803 break;
2804 }
2805 }
2806 insertAttributeSetAt(style, counter);
2807 extendedIndex++;
2808 }
2809
2810 /**
2811 * Removes a previously added style. This will do nothing if
2812 * <code>style</code> is not referenced by the receiver.
2813 */
2814 synchronized void removeStyle(Style style) {
2815 AttributeSet[] attrs = getAttributes();
2816
2817 for (int counter = attrs.length - 1; counter >= 0; counter--) {
2818 if (attrs[counter] == style) {
2819 removeAttributeSetAt(counter);
2820 if (counter < extendedIndex) {
2821 extendedIndex--;
2822 }
2823 break;
2824 }
2825 }
2826 }
2827
2828 /**
2829 * Adds <code>s</code> as one of the Attributesets to look up
2830 * attributes in.
2831 */
2832 synchronized void insertExtendedStyleAt(Style attr, int index) {
2833 insertAttributeSetAt(attr, extendedIndex + index);
2834 }
2835
2836 /**
2837 * Adds <code>s</code> as one of the AttributeSets to look up
2838 * attributes in. It will be the AttributeSet last checked.
2839 */
2840 synchronized void addExtendedStyle(Style attr) {
2841 insertAttributeSetAt(attr, getAttributes().length);
2842 }
2843
2844 /**
2845 * Removes the style at <code>index</code> +
2846 * <code>extendedIndex</code>.
2847 */
2848 synchronized void removeExtendedStyleAt(int index) {
2849 removeAttributeSetAt(extendedIndex + index);
2850 }
2851
2852 /**
2853 * Returns true if the receiver matches <code>selector</code>, where
2854 * a match is defined by the CSS rule matching.
2855 * Each simple selector must be separated by a single space.
2856 */
2857 protected boolean matches(String selector) {
2858 int sLast = selector.length();
2859
2860 if (sLast == 0) {
2861 return false;
2862 }
2863 int thisLast = name.length();
2864 int sCurrent = selector.lastIndexOf(' ');
2865 int thisCurrent = name.lastIndexOf(' ');
2866 if (sCurrent >= 0) {
2867 sCurrent++;
2868 }
2869 if (thisCurrent >= 0) {
2870 thisCurrent++;
2871 }
2872 if (!matches(selector, sCurrent, sLast, thisCurrent, thisLast)) {
2873 return false;
2874 }
2875 while (sCurrent != -1) {
2876 sLast = sCurrent - 1;
2877 sCurrent = selector.lastIndexOf(' ', sLast - 1);
2878 if (sCurrent >= 0) {
2879 sCurrent++;
2880 }
2881 boolean match = false;
2882 while (!match && thisCurrent != -1) {
2883 thisLast = thisCurrent - 1;
2884 thisCurrent = name.lastIndexOf(' ', thisLast - 1);
2885 if (thisCurrent >= 0) {
2886 thisCurrent++;
2887 }
2888 match = matches(selector, sCurrent, sLast, thisCurrent,
2889 thisLast);
2890 }
2891 if (!match) {
2892 return false;
2893 }
2894 }
2895 return true;
2896 }
2897
2898 /**
2899 * Returns true if the substring of the receiver, in the range
2900 * thisCurrent, thisLast matches the substring of selector in
2901 * the ranme sCurrent to sLast based on CSS selector matching.
2902 */
2903 boolean matches(String selector, int sCurrent, int sLast,
2904 int thisCurrent, int thisLast) {
2905 sCurrent = Math.max(sCurrent, 0);
2906 thisCurrent = Math.max(thisCurrent, 0);
2907 int thisDotIndex = boundedIndexOf(name, '.', thisCurrent,
2908 thisLast);
2909 int thisPoundIndex = boundedIndexOf(name, '#', thisCurrent,
2910 thisLast);
2911 int sDotIndex = boundedIndexOf(selector, '.', sCurrent, sLast);
2912 int sPoundIndex = boundedIndexOf(selector, '#', sCurrent, sLast);
2913 if (sDotIndex != -1) {
2914 // Selector has a '.', which indicates name must match it,
2915 // or if the '.' starts the selector than name must have
2916 // the same class (doesn't matter what element name).
2917 if (thisDotIndex == -1) {
2918 return false;
2919 }
2920 if (sCurrent == sDotIndex) {
2921 if ((thisLast - thisDotIndex) != (sLast - sDotIndex) ||
2922 !selector.regionMatches(sCurrent, name, thisDotIndex,
2923 (thisLast - thisDotIndex))) {
2924 return false;
2925 }
2926 }
2927 else {
2928 // Has to fully match.
2929 if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
2930 !selector.regionMatches(sCurrent, name, thisCurrent,
2931 (thisLast - thisCurrent))) {
2932 return false;
2933 }
2934 }
2935 return true;
2936 }
2937 if (sPoundIndex != -1) {
2938 // Selector has a '#', which indicates name must match it,
2939 // or if the '#' starts the selector than name must have
2940 // the same id (doesn't matter what element name).
2941 if (thisPoundIndex == -1) {
2942 return false;
2943 }
2944 if (sCurrent == sPoundIndex) {
2945 if ((thisLast - thisPoundIndex) !=(sLast - sPoundIndex) ||
2946 !selector.regionMatches(sCurrent, name, thisPoundIndex,
2947 (thisLast - thisPoundIndex))) {
2948 return false;
2949 }
2950 }
2951 else {
2952 // Has to fully match.
2953 if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
2954 !selector.regionMatches(sCurrent, name, thisCurrent,
2955 (thisLast - thisCurrent))) {
2956 return false;
2957 }
2958 }
2959 return true;
2960 }
2961 if (thisDotIndex != -1) {
2962 // Reciever references a class, just check element name.
2963 return (((thisDotIndex - thisCurrent) == (sLast - sCurrent)) &&
2964 selector.regionMatches(sCurrent, name, thisCurrent,
2965 thisDotIndex - thisCurrent));
2966 }
2967 if (thisPoundIndex != -1) {
2968 // Reciever references an id, just check element name.
2969 return (((thisPoundIndex - thisCurrent) ==(sLast - sCurrent))&&
2970 selector.regionMatches(sCurrent, name, thisCurrent,
2971 thisPoundIndex - thisCurrent));
2972 }
2973 // Fail through, no classes or ides, just check string.
2974 return (((thisLast - thisCurrent) == (sLast - sCurrent)) &&
2975 selector.regionMatches(sCurrent, name, thisCurrent,
2976 thisLast - thisCurrent));
2977 }
2978
2979 /**
2980 * Similiar to String.indexOf, but allows an upper bound
2981 * (this is slower in that it will still check string starting at
2982 * start.
2983 */
2984 int boundedIndexOf(String string, char search, int start,
2985 int end) {
2986 int retValue = string.indexOf(search, start);
2987 if (retValue >= end) {
2988 return -1;
2989 }
2990 return retValue;
2991 }
2992
2993 public void addAttribute(Object name, Object value) {}
2994 public void addAttributes(AttributeSet attributes) {}
2995 public void removeAttribute(Object name) {}
2996 public void removeAttributes(Enumeration<?> names) {}
2997 public void removeAttributes(AttributeSet attributes) {}
2998 public void setResolveParent(AttributeSet parent) {}
2999 public String getName() {return name;}
3000 public void addChangeListener(ChangeListener l) {}
3001 public void removeChangeListener(ChangeListener l) {}
3002 public ChangeListener[] getChangeListeners() {
3003 return new ChangeListener[0];
3004 }
3005
3006 /** The name of the Style, which is the selector.
3007 * This will NEVER change!
3008 */
3009 String name;
3010 /** Start index of styles coming from other StyleSheets. */
3011 private int extendedIndex;
3012 }
3013
3014
3015 /**
3016 * SelectorMapping contains a specifitiy, as an integer, and an associated
3017 * Style. It can also reference children <code>SelectorMapping</code>s,
3018 * so that it behaves like a tree.
3019 * <p>
3020 * This is not thread safe, it is assumed the caller will take the
3021 * necessary precations if this is to be used in a threaded environment.
3022 */
3023 static class SelectorMapping implements Serializable {
3024 public SelectorMapping(int specificity) {
3025 this.specificity = specificity;
3026 }
3027
3028 /**
3029 * Returns the specificity this mapping represents.
3030 */
3031 public int getSpecificity() {
3032 return specificity;
3033 }
3034
3035 /**
3036 * Sets the Style associated with this mapping.
3037 */
3038 public void setStyle(Style style) {
3039 this.style = style;
3040 }
3041
3042 /**
3043 * Returns the Style associated with this mapping.
3044 */
3045 public Style getStyle() {
3046 return style;
3047 }
3048
3049 /**
3050 * Returns the child mapping identified by the simple selector
3051 * <code>selector</code>. If a child mapping does not exist for
3052 *<code>selector</code>, and <code>create</code> is true, a new
3053 * one will be created.
3054 */
3055 public SelectorMapping getChildSelectorMapping(String selector,
3056 boolean create) {
3057 SelectorMapping retValue = null;
3058
3059 if (children != null) {
3060 retValue = children.get(selector);
3061 }
3062 else if (create) {
3063 children = new HashMap<String, SelectorMapping>(7);
3064 }
3065 if (retValue == null && create) {
3066 int specificity = getChildSpecificity(selector);
3067
3068 retValue = createChildSelectorMapping(specificity);
3069 children.put(selector, retValue);
3070 }
3071 return retValue;
3072 }
3073
3074 /**
3075 * Creates a child <code>SelectorMapping</code> with the specified
3076 * <code>specificity</code>.
3077 */
3078 protected SelectorMapping createChildSelectorMapping(int specificity) {
3079 return new SelectorMapping(specificity);
3080 }
3081
3082 /**
3083 * Returns the specificity for the child selector
3084 * <code>selector</code>.
3085 */
3086 protected int getChildSpecificity(String selector) {
3087 // class (.) 100
3088 // id (#) 10000
3089 char firstChar = selector.charAt(0);
3090 int specificity = getSpecificity();
3091
3092 if (firstChar == '.') {
3093 specificity += 100;
3094 }
3095 else if (firstChar == '#') {
3096 specificity += 10000;
3097 }
3098 else {
3099 specificity += 1;
3100 if (selector.indexOf('.') != -1) {
3101 specificity += 100;
3102 }
3103 if (selector.indexOf('#') != -1) {
3104 specificity += 10000;
3105 }
3106 }
3107 return specificity;
3108 }
3109
3110 /**
3111 * The specificity for this selector.
3112 */
3113 private int specificity;
3114 /**
3115 * Style for this selector.
3116 */
3117 private Style style;
3118 /**
3119 * Any sub selectors. Key will be String, and value will be
3120 * another SelectorMapping.
3121 */
3122 private HashMap<String, SelectorMapping> children;
3123 }
3124
3125
3126 // ---- Variables ---------------------------------------------
3127
3128 final static int DEFAULT_FONT_SIZE = 3;
3129
3130 private CSS css;
3131
3132 /**
3133 * An inverted graph of the selectors.
3134 */
3135 private SelectorMapping selectorMapping;
3136
3137 /** Maps from selector (as a string) to Style that includes all
3138 * relevant styles. */
3139 private Hashtable<String, ResolvedStyle> resolvedStyles;
3140
3141 /** Vector of StyleSheets that the rules are to reference.
3142 */
3143 private Vector<StyleSheet> linkedStyleSheets;
3144
3145 /** Where the style sheet was found. Used for relative imports. */
3146 private URL base;
3147
3148
3149 /**
3150 * Default parser for CSS specifications that get loaded into
3151 * the StyleSheet.<p>
3152 * This class is NOT thread safe, do not ask it to parse while it is
3153 * in the middle of parsing.
3154 */
3155 class CssParser implements CSSParser.CSSParserCallback {
3156
3157 /**
3158 * Parses the passed in CSS declaration into an AttributeSet.
3159 */
3160 public AttributeSet parseDeclaration(String string) {
3161 try {
3162 return parseDeclaration(new StringReader(string));
3163 } catch (IOException ioe) {}
3164 return null;
3165 }
3166
3167 /**
3168 * Parses the passed in CSS declaration into an AttributeSet.
3169 */
3170 public AttributeSet parseDeclaration(Reader r) throws IOException {
3171 parse(base, r, true, false);
3172 return declaration.copyAttributes();
3173 }
3174
3175 /**
3176 * Parse the given CSS stream
3177 */
3178 public void parse(URL base, Reader r, boolean parseDeclaration,
3179 boolean isLink) throws IOException {
3180 this.base = base;
3181 this.isLink = isLink;
3182 this.parsingDeclaration = parseDeclaration;
3183 declaration.removeAttributes(declaration);
3184 selectorTokens.removeAllElements();
3185 selectors.removeAllElements();
3186 propertyName = null;
3187 parser.parse(r, this, parseDeclaration);
3188 }
3189
3190 //
3191 // CSSParserCallback methods, public to implement the interface.
3192 //
3193
3194 /**
3195 * Invoked when a valid @import is encountered, will call
3196 * <code>importStyleSheet</code> if a
3197 * <code>MalformedURLException</code> is not thrown in creating
3198 * the URL.
3199 */
3200 public void handleImport(String importString) {
3201 URL url = CSS.getURL(base, importString);
3202 if (url != null) {
3203 importStyleSheet(url);
3204 }
3205 }
3206
3207 /**
3208 * A selector has been encountered.
3209 */
3210 public void handleSelector(String selector) {
3211 //class and index selectors are case sensitive
3212 if (!(selector.startsWith(".")
3213 || selector.startsWith("#"))) {
3214 selector = selector.toLowerCase();
3215 }
3216 int length = selector.length();
3217
3218 if (selector.endsWith(",")) {
3219 if (length > 1) {
3220 selector = selector.substring(0, length - 1);
3221 selectorTokens.addElement(selector);
3222 }
3223 addSelector();
3224 }
3225 else if (length > 0) {
3226 selectorTokens.addElement(selector);
3227 }
3228 }
3229
3230 /**
3231 * Invoked when the start of a rule is encountered.
3232 */
3233 public void startRule() {
3234 if (selectorTokens.size() > 0) {
3235 addSelector();
3236 }
3237 propertyName = null;
3238 }
3239
3240 /**
3241 * Invoked when a property name is encountered.
3242 */
3243 public void handleProperty(String property) {
3244 propertyName = property;
3245 }
3246
3247 /**
3248 * Invoked when a property value is encountered.
3249 */
3250 public void handleValue(String value) {
3251 if (propertyName != null && value != null && value.length() > 0) {
3252 CSS.Attribute cssKey = CSS.getAttribute(propertyName);
3253 if (cssKey != null) {
3254 // There is currently no mechanism to determine real
3255 // base that style sheet was loaded from. For the time
3256 // being, this maps for LIST_STYLE_IMAGE, which appear
3257 // to be the only one that currently matters. A more
3258 // general mechanism is definately needed.
3259 if (cssKey == CSS.Attribute.LIST_STYLE_IMAGE) {
3260 if (value != null && !value.equals("none")) {
3261 URL url = CSS.getURL(base, value);
3262
3263 if (url != null) {
3264 value = url.toString();
3265 }
3266 }
3267 }
3268 addCSSAttribute(declaration, cssKey, value);
3269 }
3270 propertyName = null;
3271 }
3272 }
3273
3274 /**
3275 * Invoked when the end of a rule is encountered.
3276 */
3277 public void endRule() {
3278 int n = selectors.size();
3279 for (int i = 0; i < n; i++) {
3280 String[] selector = selectors.elementAt(i);
3281 if (selector.length > 0) {
3282 StyleSheet.this.addRule(selector, declaration, isLink);
3283 }
3284 }
3285 declaration.removeAttributes(declaration);
3286 selectors.removeAllElements();
3287 }
3288
3289 private void addSelector() {
3290 String[] selector = new String[selectorTokens.size()];
3291 selectorTokens.copyInto(selector);
3292 selectors.addElement(selector);
3293 selectorTokens.removeAllElements();
3294 }
3295
3296
3297 Vector<String[]> selectors = new Vector<String[]>();
3298 Vector<String> selectorTokens = new Vector<String>();
3299 /** Name of the current property. */
3300 String propertyName;
3301 MutableAttributeSet declaration = new SimpleAttributeSet();
3302 /** True if parsing a declaration, that is the Reader will not
3303 * contain a selector. */
3304 boolean parsingDeclaration;
3305 /** True if the attributes are coming from a linked/imported style. */
3306 boolean isLink;
3307 /** Where the CSS stylesheet lives. */
3308 URL base;
3309 CSSParser parser = new CSSParser();
3310 }
3311
3312 void rebaseSizeMap(int base) {
3313 final int minimalFontSize = 4;
3314 sizeMap = new int[sizeMapDefault.length];
3315 for (int i = 0; i < sizeMapDefault.length; i++) {
3316 sizeMap[i] = Math.max(base * sizeMapDefault[i] /
3317 sizeMapDefault[CSS.baseFontSizeIndex],
3318 minimalFontSize);
3319 }
3320
3321 }
3322
3323 int[] getSizeMap() {
3324 return sizeMap;
3325 }
3326 boolean isW3CLengthUnits() {
3327 return w3cLengthUnits;
3328 }
3329
3330 /**
3331 * The HTML/CSS size model has seven slots
3332 * that one can assign sizes to.
3333 */
3334 static final int sizeMapDefault[] = { 8, 10, 12, 14, 18, 24, 36 };
3335
3336 private int sizeMap[] = sizeMapDefault;
3337 private boolean w3cLengthUnits = false;
3338 }